如何利用webpack来提升前端开发效率(二)?

栏目: 编程语言 · 发布时间: 7年前

内容简介:通过接上篇文章,我们的目录结构,如图所示:如何我们从网上随意下载了一种字体,放置于

通过 如何利用webpack来提升前端开发效率(一) 的学习,我们已经能够通过 webpackloaderpiugin 机制来处理各种文件资源。细心的小伙伴们发现了缺少了对字体文件和 HTML<img> 标签的资源处理,那让我们先来解决这个问题。

接上篇文章,我们的目录结构,如图所示:

如何利用webpack来提升前端开发效率(二)?
首先是对字体文件的处理,修改 webpack.config.js
// webpack.config.js
// 新增对字体的loader
      {
        test: /\.(eot|woff|woff2|ttf)$/,
        use: [{
          loader: 'url-loader',
          options: {
            name: '[name].[hash:7].[ext]',
            limit: 8192,
            outputPath: 'font',  // 打包到 dist/font 目录下
          }
        }]
      },
复制代码

如何我们从网上随意下载了一种字体,放置于 src 文件夹下,并修改 src/index.html

<!-- src/index.html -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>my-webpack</title>
</head>

<body>
  <h1>webpack大法好!!前端大法好!!</h1>
</body>

</html>
复制代码

index.scss 中引入字体

/* src/index.scss */
/* 添加以下样式 */
@font-face {
  font-family: 'myFont';
  src: url('./font/ZaoZiGongFangQiaoPinTi-2.ttf');
}

h1 {
  font-family: 'myFont';
}
复制代码

在此之前,每次重新打包都要删除 dist 文件夹,实在是麻烦,现在我们可以借助 clean-webpack-plugin ,它能够在每次打包时删除指定的文件夹,我们在命令行执行 npm i clean-webpack-plugin -D

修改 webpack.config.js

// webpack.config.js
// 新增以下引入
const CleanWebpackPlugin = require('clean-webpack-plugin');

// 新增以下插件
plugins: [
    new CleanWebpackPlugin(['dist'])
],
复制代码

随后在命令行执行 npm run build ,我们的 dist 文件夹会被自动删除,并输出以下结果,可以看到我们虽然成功打包了字体文件,但字体文件是在太大,连 webpack 都发出了警告 [big]

如何利用webpack来提升前端开发效率(二)?
如何利用webpack来提升前端开发效率(二)?

这里我们一般有以下解决方案:

CDN
font-spider

我们实践一下第三种方案,也是我推荐的方案 在命令行依次执行

npm i font-spider -D
font-spider ./dist/index.html
复制代码

可以看到将近 4MB 的字体文件体积瞬间压缩至不足 6KB !!!而页面效果和之前一模一样。

如何利用webpack来提升前端开发效率(二)?
而对于 HTML 文档中 <img>标签的引入问题 ,我们需要借助 html-loader ,它能将 HTML 文档中 img.src 解析成 require ,从而实现引入图片,话不多说,我们直接看效果。在命令行执行 npm i html-loader -D

修改以下文件

// webpack.config.js
// 新增对html的loader
      {
        test: /\.html$/,
        use: {
          loader: 'html-loader',
          options: {
            attrs: ['img:src'] // img代表解析标签,src代表要解析的值,以key:value形式存在于attrs数组中
          }
        }
      }
复制代码
<!-- src/index.html -->
<body>
    +  <img src="./leaf.png" alt="">
</body>
复制代码

在命令行执行 npm run build ,查看 dist/index.html ,看来已经成功啦

如何利用webpack来提升前端开发效率(二)?

动态加载

设想如果我们的入口文件很大(包含了所有的业务逻辑代码),就会造成首屏加载变慢,用户体验感下降。 这里我们从两个方面解决:

  • 模块解耦,将入口文件解耦,将基础模块(UI,工具类)和业务模块分离,即能方便代码维护拓展,也能减少入口文件的体积。
  • 动态加载,用户不可能一开始就用到所有的功能,这时候我们可以将次要的,需要事件触发的模块,在之后的交互过程中,动态引入。 在 src 目录下新增 dynamic.js
// dynamic.js
export default () => {
  console.log('Im dynamically loaded.');
}
复制代码

修改以下文件

<!-- src/index.html -->
<body>
  + <button id="btn">点击我,动态加载dynamic.js</button>
</body>
复制代码
// src/index.js
// 新增以下内容
const btn = document.getElementById('btn');
// 点击按钮,动态加载dynamic.js
btn.onclick = () => {
  import(/* webpackChunkName: "dynamic" */ './dynamic.js').then(function (module) {
    const fn = module.default;
    fn();
  })
}
复制代码

执行 npm run build ,可以看到

如何利用webpack来提升前端开发效率(二)?
如果未设置 /* webpackChunkName: "dynamic" */

,则是

如何利用webpack来提升前端开发效率(二)?
可以得出得结论是:设置 ChunkName"dynamic" 是必要的,否则打包完成会是以自动分配的、可读性很差的 id 命名的 JS 文件。且没有 Chunk Names

标识。

现在我们打开 dist/index.html ,此时

如何利用webpack来提升前端开发效率(二)?

当我点击该按钮时

如何利用webpack来提升前端开发效率(二)?

控制台打印出

如何利用webpack来提升前端开发效率(二)?
Network网络请求显示,动态加载了 dynamic.js
如何利用webpack来提升前端开发效率(二)?

至此,我们成功实现了动态加载。

分离开发环境和生产环境

回头看我们的 webpack.config.js ,不知不觉就写了这么多代码,鉴于我们在开发实际项目时,是开发和生产两套工作模式,各司其职,我们不如做个了断,分离配置。

命令行执行 npm i webpack-merge cross-env -D

webpack-merge 可以合并webpack配置项, cross-env 可以设置及使用环境变量。

新增 webpack.base.js ,提供基本的 webpack loader plugin 配置

const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const pathResolve = (targetPath) => path.resolve(__dirname, targetPath);
const devMode = process.env.NODE_ENV !== 'production';
// 在node中,有全局变量process表示的是当前的node进程。
// process.env包含着关于系统环境的信息。
// NODE_ENV是用户一个自定义的变量,在webpack中它的用途是来判断当前是生产环境或开发环境。
// 我们可以通过 cross-env 将 NODE_ENV=development 写入 npm run dev的指令中,从而注入NODE_ENV变量。

module.exports = {
  entry: {
    index: pathResolve('js/index.js')
  },
  output: {
    path: pathResolve('dist'),
  },
  module: {
    rules: [
      {
        test: /\.html$/,
        use: {
          loader: 'html-loader',
          options: {
            attrs: ['img:src']
          },
        },
      },
      {
        test: /\.(eot|woff|woff2|ttf)$/,
        use: [{
          loader: 'url-loader',
          options: {
            name: '[name].[hash:7].[ext]',
            limit: 8192,
            outputPath: 'font',
          },
        }],
      },
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          devMode ? 'style-loader' : {  // 如果处于开发模式,则无需再外链CSS,直接插入到<style>标签中
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: '../'
            }
          },
          'css-loader',
          'postcss-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.(png|jpg|jpeg|svg|gif)$/,
        use: [{
          loader: 'url-loader',
          options: {
            limit: 8192,
            name: '[name].[hash:7].[ext]',
            outputPath: 'img',
          },
        }],
      },
    ],
  },
  plugins: [
    new htmlWebpackPlugin({
      minify: {
        collapseWhitespace: true,  // 移除空格
        removeAttributeQuotes: true, // 移除引号
        removeComments: true // 移除注释
      },
      filename: pathResolve('dist/index.html'),
      template: pathResolve('src/index.html'),
    })
  ]
};
复制代码

新增 webpack.dev.js ,服务于开发模式下

const path = require('path');
const webpack = require('webpack');
const base = require('./webpack.base.js');
const { smart } = require('webpack-merge');

const pathResolve = (targetPath) => path.resolve(__dirname, targetPath);

module.exports = smart(base, {
  mode: 'development',
  output: {
    filename: 'js/[name].[hash:7].js'
  },
  devServer: {
    contentBase: pathResolve('dist'),
    port: '8080',
    inline: true,
    historyApiFallback: true,
    hot: true
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin()
  ]
})
复制代码

新增 webpack.prod.js ,服务于生产模式下

const path = require('path');
const base = require('./webpack.base.js');
const { smart } = require('webpack-merge');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const pathResolve = (targetPath) => path.resolve(__dirname, targetPath);

module.exports = smart(base, {
  mode: 'production',
  devtool: 'source-map', // 会生成对于调试的完整的.map文件,但同时也会减慢打包速度,适用于打包后的代码查错
  output: {
    filename: 'js/[name].[chunkhash:7].js',
    chunkFilename: 'js/[name].[chunkhash:7].js',
  },
  plugins: [
    new CleanWebpackPlugin(['dist']),
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:7].css',
    }),
  ],
});
复制代码

相应的, package.json 也需要修改

// 新增以下两条命令
// cross-env 决定运行环境   --config 决定运行哪个配置文件
"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config webpack.prod.js "
复制代码

缓存之争

缓存在前端的地位毋庸置疑,正确的利用缓存就能极大地提高应用的加载速度和性能。 webpack 利用了 hash 值作为文件名的组成部分,能有效利用缓存。当修改文件,重新打包时,hash值就会改变,导致缓存失效,HTTP请求重新拉取资源。

webpack 有三种hash处理策略,分别是:

hash

属于项目工程级别的,即每次修改任何一个文件,所有文件名的 hash 值都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。如将整个项目的 filename 的命名策略改为 name.[hash:7] (:7的意思是从完整hash值中截取前七位),我们可以看到,打包后的文件hash值是一样的,所以对于没有改变的模块而言,hash也被更新了,导致缓存失效了。

如何利用webpack来提升前端开发效率(二)?

chunkhash

chunkhash 根据不同的入口文件( Entry )进行依赖文件解析、构建对应的 chunk ,生成对应的哈希值。如将整个项目 filename 的命名策略改为 name.[chunkhash:7] ,我们可以看到 Chunk Names"index" 的文件hash值一致,而不同 chunkhash 值不同。这也就避免了修改某个文件,整个工程hash值都将改变的情况。

如何利用webpack来提升前端开发效率(二)?

contenthash

但问题随之而来, index.scss 是作为模块导入到 index.js 中的,其 chunkhash 值是一致的,只要其中之一改变,与其关联的文件 chunkhash 值也会改变。这时候就要用到 contenthash ,它是根据文件的内容计算,该文件的内容改变了,contenthash值才会改变。我们将css文件的命名策略改为 name.[contenthash:7] ,并修改 src/index.js ,不改动其他文件,再次打包,发现:

如何利用webpack来提升前端开发效率(二)?
如何利用webpack来提升前端开发效率(二)?

生产环境的配置优化

tree-shaking

字面意思理解为从一棵树上把叶子摇晃下来,这样数的重量就减轻了,类比程序,就如同从我们的应用上删除没用的代码,从而减少体积。借于 ES6 的模块引入是静态分析的,故而 webpack 可以在编译时正确判断到底加载了什么代码,即没有被引用的模块不会被打包进来,减少我们的包大小,缩小应用的加载时间,呈现给用户更佳的体验。那么怎么使用呢?

新建 src/utils.js

// src/utils.js
const square = (num) => num ** 2;
const cube = num => num * num * num;
// 导出了两个方法
export {
  square,
  cube
}
复制代码

新建 src/shake.js

// src/shake.js
import { cube } from './utils.js';
// 只使用了cube方法
console.log('cube(3) is' + cube(3));
复制代码

webpack.base.js 中新增入口文件 shake.js

entry: {
    +  shake: pathResolve('src/shake.js')
  },
复制代码

命令行执行 npm run build ,查看打包后的 shake.js ,并没有发现 square 方法没有被打包进来,说明 tree-shaking 起作用了。 而这一切都是 webpackproduction 环境下自动为我们实现的。

如何利用webpack来提升前端开发效率(二)?

splitChunks

字面意思为拆分代码块,默认情况下它将只会影响按需加载的代码块,因为改变初始化的代码块将会影响 HTML 中运行项目需要包含的 script 标签。还记得我们在 src/index.js 中动态引入了 src/dynamic.js 吗,最终 dynamic.js 被独立打包,就是归功于 splitChunks

在实际生产中,我们经常会引入第三方库( JQueryLodash ),往往这些第三方库体积高达几十KB掺杂在业务代码中,并且不会像业务代码一样经常更新,这时候我们就需要将他们拆分出来,既能保持第三方库持久缓存,又能缩减业务代码的体积。

修改 webpack.prod.js

// 在module.exports中新增如下内容
  optimization: {
    runtimeChunk: {
      name: 'manifest', // 被注入了webpackJsonp的定义及异步加载相关的定义,单独打包模块信息清单,利于缓存
    },
    splitChunks: {
      cacheGroups: { // 缓存组,默认将所有来源于node_modules的模块分配到叫做'venders'的缓存组,所有引用超过两次的模块分配到'default'缓存组.
        vendor: {
          chunks: "all",                    // all, async, initial 三选一, 插件作用的chunks范围,推荐all
          test: /[\\/]node_modules[\\/]/,   // 缓存组所选择的的模块范围
          name: "vendor",                   // Chunk Names及打包出来的文件名
          minChunks: 1,                     // 引用次数>=1
          maxInitialRequests: 5,            // 页面初始化时加载代码块的请求数量应该<=5
          minSize: 0,                       // 代码块的最小尺寸
          priority: 100,                    // 缓存优先级权重
        },
      }
    }
  },
复制代码

命令行执行 npm i lodash -S

修改 src/index.js

// 新增以下内容
import _ from 'lodash';
复制代码

执行 npm run build ,可以看到优化前 lodash 被打包进 index.js ,优化后 lodash 被打包进 vendor.js

如何利用webpack来提升前端开发效率(二)?
如何利用webpack来提升前端开发效率(二)?

压缩代码,去除冗余

往往在CSS代码中,存在很多我们没有用到的样式,它们是冗余的,我们需要将它们剔除,并压缩剩余的CSS样式,以减少CSS文件体积。

在命令行执行 npm i glob optimize-css-assets-webpack-plugin purifycss-webpack purify-css -D

修改 webpack.prod.js

// 新增以下引入
const glob = require('glob'); // 匹配所需文件
const PurifyCssWebpack = require('purifycss-webpack'); // 去除冗余CSS
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); // 压缩CSS

// 新增以下插件
new PurifyCssWebpack({
    paths: glob.sync(pathResolve('src/*.html')) // 同步扫描所有html文件中所引用的css,并去除冗余样式
})

// 新增以下优化
optimization: {
    minimizer: [
      new OptimizeCSSAssetsPlugin({}) // 压缩CSS
    ]
  }

复制代码

执行 npm run build

如何利用webpack来提升前端开发效率(二)?
可以看到,去除了冗余 CSS ,并压缩至一行。接下来,我们需要压缩 JS 代码。 由于我们使用的是 uglifyjs-webpack-plugin

,它需要ES6的支持,所以我们先让工程支持ES6的语法。 Babel 是一个 JavaScript 编译器。它能把下一代 JavaScript 语法转译成ES5,以适配多种运行环境。

@babel/core 提供了babel的转译API,如 babel.transform 等,用于对代码进行转译。像 webpackbabel-loader 就是调用这些API来完成转译过程的。

@babel/preset-env 可以根据配置的目标浏览器或者运行环境来自动将 ES2015+ 的代码转换为 ES5

先在命令行执行 npm i @babel/core @babel/preset-env babel-loader @babel/plugin-syntax-dynamic-import -D

新建 .babelrc 文件

{
  "presets": [  // 配置预设环境
    ["@babel/preset-env", {
      "modules": false
    }]
  ],
  "plugins": [
    "@babel/plugin-syntax-dynamic-import" // 处理src/index.js中动态加载
  ]
}
复制代码

修改 webpack.base.js

// 新增js的解析规则
    {
        test: /\.(js|jsx)$/,
        use: 'babel-loader',
        exclude: /node_modules/
    },
复制代码

然后命令行执行 npm i uglifyjs-webpack-plugin -D

修改 webpack.prod.js

// 新增以下引入
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");

// 新增以下优化
optimization: {
    minimizer: [
    +  new UglifyJsPlugin({ // 压缩JS
        cache: true,
        parallel: true,
        sourceMap: true
      })
    ]
  }
复制代码

执行 npm run build ,可以看到打包的文件体积大大减少,大功告成, JS 也被压缩了。

index.html 为例,我们可以打开Chrome的开发者工具,选择More tools,点击Coverage面板,可以看到JS、CSS等文件的使用率,配合我们定制的webpack配置进行极致优化。

如何利用webpack来提升前端开发效率(二)?
如何利用webpack来提升前端开发效率(二)?

多页面

有时候,我们需要同时构建多个页面,借助 html-webpack-plugin ,只需在 plugins 中添加新页面的配置项。

新增 src/main.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>main page</title>
</head>

<body>
  <h1>I am Main Page</h1>
</body>

</html>
复制代码

修改 webpack.base.js

// 修改以下内容
  plugins: [
    new htmlWebpackPlugin({ // 配置index.html
      minify: {
        collapseWhitespace: true,
        removeAttributeQuotes: true,
        removeComments: true
      },
      filename: pathResolve('dist/index.html'),
      template: pathResolve('src/index.html'),
      chunks: ['manifest', 'vendor', 'index', ]  // 配置index.html需要用的chunk块,即加载哪些JS文件,manifest模块管理的核心,必须第一个进行加载,不然会报错
    }),
    new htmlWebpackPlugin({ // 配置main.html
      minify: {
        collapseWhitespace: true,
        removeAttributeQuotes: true,
        removeComments: true
      },
      filename: pathResolve('dist/main.html'),
      template: pathResolve('src/main.html'),
      chunks: ['manifest', 'shake'] // 配置index.html需要用的chunk块,加载manifest.js,shake.js
    }),
  ],
复制代码

执行 npm run build ,成功构建了 index.htmlmain.html

如何利用webpack来提升前端开发效率(二)?
如何利用webpack来提升前端开发效率(二)?

结语

至此,我们摆脱了第三方脚手架的的禁锢,循序渐进的搭建了属于自己的前端流程工具,做到了即改即用,功能俱全,快速便捷,复用性强的特点。希望小伙伴能亲自动手,别老是纸上谈 webpack ,要理解它的构建、优化原理,得心应手得融入到自己的工程项目中,拒绝再用以前繁琐,不规范的开发流程,不做“CV工程师”,创建属于自己的知识体系、工作流程,提高前端的开发效率。

最后,本项目源码已部署在 Github 上,并增加了许多额外优化( less 的支持, ESLint 检测,针对图片格式的压缩...),让大家可以直接下载体验,并协助项目开发,日后也会持续维护,希望小伙伴们可以互相学习,提提建议。

easy-frontend 一个快速,简单,易用的前端开发效率提升工具

Star该项目,就是你们对我最大的的鼓励!!

前端路上,不忘初心,祝大家早日发财!!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

软件测试的艺术

软件测试的艺术

梅尔斯 / 机械工业出版社 / 2006年01月 / 22.0

《软件测试的艺术》(原书第2版)成功、有效地进行软件测试的实用策略和技术:    基本的测试原理和策略      验收测试    程序检查和走查         安装测试    代码检查            模块(单元)测试    错误列表            测试规划与控制    同行评分            独立测试机构    黑盒、白盒测试    ......一起来看看 《软件测试的艺术》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具