【Webpack】一些配置优化与解决方案

TiJay

开始

官网是最好的学习资料,本篇文章略过入门配置这些内容,整理了一些常用的配置点。

在 webpack 打包过程查询的依赖关系:

  • ES2015 import 语句
  • CommonJS require() 语句
  • AMD define 和 require 语句
  • css/sass/less 文件中的 @import 语句。
  • 样式(url(...))或 HTML 文件(<img src=...>)中的图片链接

从入口起点(entry) 开始,webpack 根据文件的这些依赖递归地构建一个依赖图 ,将这个依赖图中所有这些模块打包输出(output)为少量的 bundle 到 /dist 或 /build 目录中。

一、module 的加载

webpack 默认从入口开始,一切文件都是模块,通过 module 配置处理各种类型的文件(js, css, sass, jpg, png....)。

加载 webpack 依赖图中的不同类型模块,需要不同的预处理器(loader)。果不加loader 会有“ ModuleParseError: Module parse failed "等错误。

需要在 module.rules 中为各类文件配置 loader,例如加载 css 和图片时 loader 安装和配置:

npm install --save-dev style-loader css-loader file-loader
// webpack.config.js

  const path = require('path');

  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: ['style-loader','css-loader']
        },
        {
          test: /\.(png|svg|jpg|gif)$/,
          use: ['file-loader']
        }
      ]
    }
  };

在打包中遇到非JS模块时,会在 module.rules 规则中匹配文件后缀名, 然后用 Rules.use 指定的的预处理器(loader)解析该文件。use 加载器可以链式传递,从右向左进行应用到模块上

可以尝试打包自定义文件类型,只需要安装配置对应的预处理器(loader)。

忽略匹配的文件

有一种场景是项目依赖的资源不需要加载解析,例如一些地图的API或JS库。

这时可以使用 module.noParse 防止 webpack 解析与正则表达式相匹配的文件,忽略的文件中的 import, require, define 等导入机制,配置方式如下:

  module: {
    noParse: /jquery|lodash/,
    
    // 从 webpack 3.0 开始,可以使用函数,如下所示:
    // noParse: function(content) {
    //   return /jquery|lodash/.test(content);
    // }
  }

忽略大型的 library 可以提高构建性能。

二、优化 CSS 相关配置

2.1 PostCss 预处理

PostCss 是一个 CSS 的预处理工具,可以帮助我们给 CSS3 的属性添加前缀(autoprefixer),样式格式校验(stylelint),提前使用 css 的新特性(postcss-cssnext),更重要的是可以实现 CSS 的模块化,防止 CSS 样式冲突。

安装 postcss-loader 和 一些 PostCss 的插件:

npm i -D postcss-loader
npm i -D autoprefixer postcss-cssnext

可以给 postcss-loader 设置多个插件:

rules: [
      {
        test: /\.(sc|c|sa)ss$/,
        use: ['style-loader', 'css-loader', 'sass-loader',
          {
            loader: "postcss-loader",
            options: {
              ident: 'postcss',
              sourceMap: true,
              plugins: loader => [
                require('autoprefixer')(),
                require('postcss-cssnext')()
              ]
            }
          }
        ]
      }
    ]

PostCss 还有很多丰富的插件可以使用。

2.2 样式拆分提取

在生产环境下将样式表抽离成专门的单独文件,使每个包含css的js文件都会创建一个CSS文件,支持按需加载css。

webpack4 使用 mini-css-extract-plugin 插件拆分样式, webpack3 之前版本可以用 extract-text-webpack-plugin 插件:

npm i -D mini-css-extract-plugin

使用 mini-css-extract-plugin 就不能再用 style-loader 注入到 html 中了。除了修改 module 配置,还需要修改plugins配置:

// webpack.product.config.js
  module: {
    rules: [ // 规则数组,修改模块的创建方式
      {
        test: /\.(sc|c|sa)ss$/,  // 正则表达式,处理scss  sass css
        use: [
          {
              MiniCssExtractPlugin.loader,
              options: {
              // 这里可以指定一个 publicPath
              // 默认使用 webpackOptions.output中的publicPath
              publicPath: '../'
             }
          },
          'css-loader',   
  //...        
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css', // 设置最终输出的文件名
      chunkFilename: '[id].css'
    })
  ]

其中的 name 是配置 output.filename 时的 name,
可以配置 pakeage.json 中的 script 命令运行生产环境下的打包配置,使用--config 指定webpack的执行脚本

 "scripts": {
    "build": "npx webpack",
    "dist": "npx webpack --config webpack.product.config.js"
  }

打包后新增 dist/main.css,而 html 中的样式失效了,因为没有 style 注入了,这时需要手动在 html 中引入 main.css 文件。

注意这个插件应该只用在 mode: 'production' 配置中,并且在 loaders 链中不能使用 style-loader, 特别是不支持开发时的 HMR。

对于 JS 代码拆分可以使用 CommonsChunkPlugin 插件,配置方式都类似。

2.3 文件压缩

webpack4 可以使用插件 optimize-css-assets-webpack-plugin 压缩文件,注意 webpack4 才有minimizer这个配置项:

npm i -D optimize-css-assets-webpack-plugin
// webpack.product.config.js
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
//...
  optimization: {
    minimizer: [new OptimizeCSSAssetsPlugin({})] // // 压缩 css 文件
  },
  plugins: [
    new MiniCssExtractPlugin({ // 拆分 CSS
      filename: "[name].css",
      chunkFilename: "[id].css"
    }),
  ],
 }

同理压缩 JS 也需要一个插件 uglifyjs-webpack-plugin, 这些压缩插件插件需要一个前提就是:mode: 'production':

npm i -D uglifyjs-webpack-plugin
// webpack.product.config.js
+ const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); // 压缩js

module.exports = {
  ...
  optimization: {
    minimizer: [
+     new UglifyJsPlugin({ // 压缩 js
        cache: true,
        parallel: true,
        sourceMap: true // set to true if you want JS source maps
      }),
      new OptimizeCSSAssetsPlugin({}) // 压缩 css 文件
    ]
  }
  ···
};

这里需要注意的是,如果没有 bable 兼容 ES6 语法,则会报" ERROR Unexpected token "等。

三、图片处理与优化

3.1 加载图片与字体

使用file-loader处理文件的导入:

npm install --save-dev file-loader
module.exports = {
  ...
  module: {
    rules: [ 
      ...
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: ['file-loader']
      }
      ···
    ]  
  } 
}     

由于 css 中可能引用到自定义的字体,处理也是跟图片一致。

test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [ 'file-loader' ]

3.2 压缩图片

file-loader将图片拷贝到dist目录并更新引用路径,进一步使用 image-webpack-loader 对图片进行压缩和优化,按照 NPM 官网文档进行安装配置:

npm install image-webpack-loader --save-dev
module.exports = {
  ...
  module: {
    rules: [ 
      ...
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: ['file-loader',
          {
            loader: 'image-webpack-loader',
            options: {
              mozjpeg: {
                progressive: true,
                quality: 65
              },
              // optipng.enabled: false will disable optipng
              optipng: {
                enabled: false,
              },
              pngquant: {
                quality: '65-90',
                speed: 4
              },
              gifsicle: {
                interlaced: false,
              },
              // the webp option will enable WEBP
              webp: {
                quality: 75
              }
            }
          }
        ]
      }
     ···
    ]  
  } 
}

原始图片content-length: 557478,大概557K,重新打包压缩后171K

3.3 小图片处理为 base64 减少 http 请求

url-loader 功能类似于 file-loader,可以把 url 地址对应的文件,打包成 base64 的 Data URI scheme,提高访问的效率。使用base64编码的图片和超链接方式的代码分别如下:

<img src="data:image/gif;base64,R0lGODlhAwADAIABAL6+vv///yH5BAEAAAEALAAAAAADAAMAAAIDjA9WADs=" />

<img src="http://gpeng.win/test.png" />

对于比较小的图片可以直接打包成 base64 从而减少http请求次数,在最新版的浏览器特别是移动端对base64的兼容性都非常好,可以放心使用。

npm install --save-dev url-loader
module.exports = {
  ...
  module: {
    rules: [ 
      ...
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          // 'file-loader',
          {
            loader: 'url-loader', // 根据图片大小,把图片优化成base64
            options: {
              limit: 10000 // 10KB
            }
          },
          ···
        ]
      }
      ···
    ]  
  } 
}     

四、解决缓存问题

4.1 配置文件 Hash 名

因为浏览器的缓存策略,如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本,所以在打包新版本后浏览器可能依旧使用之前的文件。

解决缓存的问题,一种方式就是修改文件名,每次打包文件都更新文件名。在出口(output)和管理输出的 plugins 中配置文件的 Hash 名:

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: 'main.[hash].js',
    path: path.resolve(__dirname, './dist')
  },
  ···  
  plugins: [
    new MiniCssExtractPlugin({ // 拆分css
      filename: '[name].[hash].css', // 设置最终输出的文件名
      chunkFilename: '[id].[hash].css'
    })
  ],
  ···  
};

修改filename: '[name].[hash].css'

> npx webpack --config webpack.product.config.js
Hash: 10c0c7348792960894f6
Version: webpack 4.30.0

这时 dist/main.10c0c7348792960894f6.css出现。

4.2 文件 Hash 名的注入

在前面拆分CSS或JS为单独文件时,HTML是手动引入的,但Hash生成的文件每次都不同,要如何自动引入?

使用 HtmlWebpackPlugin 插件,可以把打包后的 CSS 或者 JS 文件引用直接注入到 HTML 模板中,这样就不用每次手动修改文件引用了。

npm i -D html-webpack-plugin
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
···
module.exports = {
  mode: 'production',
  ···  
  plugins: [
    ···
+   new HtmlWebpackPlugin({
      title: 'Learn webpack', // 默认值:Webpack App
      filename: 'index.html', // 最终生成的文件,默认值: 'index.html'
      template: path.resolve(__dirname, 'src/index.html'), // 模版
      minify: {
        collapseWhitespace: true, // 折叠空白
        removeComments: true, // 移除注释
        removeAttributeQuotes: true // 移除属性的引号
      }
    })
  ],
  ···  
};

可以在新建 src/index.html 作为模版,执行打包命令:

> npm run dist

> testwebpack@1.0.0 dist /Users/TJing/Documents/webProjects/testWebpack
> npx webpack --config webpack.product.config.js

Hash: b6f4a880e0371e2f8ad3

最终打包生成 main.b6f4a880e0371e2f8ad3.css、main.b6f4a880e0371e2f8ad3.js 和 index.html,此时HTML自动注入了CSS和JS:

<!DOCTYPE html>
<html lang=en>
<head>
  <meta charset=UTF-8>
  <title></title>
  <link href=main.b6f4a880e0371e2f8ad3.css rel=stylesheet>
</head>
<body>
<script type=text/javascript src=main.b6f4a880e0371e2f8ad3.js></script>
</body>
</html>

4.3 清理 dist 打包目录

每次编译后 /dist 文件夹都会保存生成的文件,特别是配置文件民Hash值后。通常推荐在每次构建前清理 /dist 文件夹。

clean-webpack-plugin 是一个比较普及的管理插件,安装和配置如下:

npm i -D clean-webpack-plugin
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
  mode: 'production',
···  
  plugins: [
+   new CleanWebpackPlugin()
      ...
  ],
···  
};

五、开发辅助

5.1 Sourse map 源码追踪

webpack会把三个源文件(a.js, b.js 和 c.js)打包到一个 bundle(bundle.js)中,在开发环境下很难 debug 到原始的开发代码,这时就要使用 source map 解决开发代码与实际运行代码不一致的问题。

在 devtool 设置 source map 增强调试过程,source map 类型传送门

// webpack.config.js

  module.exports = {
    entry: './src/index.js',
    // ...
    devtool: 'inline-source-map',
  };

还可以使用 SourceMapDevToolPlugin 插件替代 devtool 选项进行更细粒度的配置;

对于样式调试,webpack 会把 css、sass 模块打包到 style 模块中,css-loader 和 sass-loader 都可以通过该 options 设置启用 sourcemap。

// webpack.config.js
    rules: [ // 规则数组,修改模块的创建方式
      {
        test: /\.(sc|c|sa)ss$/,  // 正则表达式,处理scss sass css
        // use: ['style-loader', 'css-loader','sass-loader']
        use: ['style-loader',
          {
          loader: 'css-loader',
          options: {
            sourceMap: true}},
          {
          loader: 'sass-loader',
          options: {
            sourceMap: true}}]
      }
    ]

5.2 --watch 自动编译

每次修改完毕后都手动编译太麻烦,最简单解决的办法就是启动 watch 监控更新自动编译。

可以在启动编译时增加 --watch 开启

"scripts": {
    "watch": "npx webpack --watch --config webpack.dev.js",
    ···
  },

增加 --watch 后,每次修改后手动刷新浏览器页面,就可以看到更新内容。但如何能不刷新页面,自动更新变化呢?

5.3 webpack-dev-server 热更新

使用 webpack-dev-server ,实际就是创建一个简单的 web 服务器,能够实时重新加载(live reloading)。

npm install --save-dev webpack-dev-server

配置只在开发环境,,除了要配置 devServer 属性,还需要在 plugins 中增加两个插件:

const webpack = require('webpack');

module.exports = {
  mode: 'development',
  devtool: 'inline-source-map', // js 的 sourcemap
  devServer: {
    contentBase: './dist',
    hot: true,
    port: 9000
  },
  plugins: [
    new webpack.NamedModulesPlugin(),  // 更容易查看(patch)的依赖
    new webpack.HotModuleReplacementPlugin()  // 替换插件
  ]
 ··· 

执行以下命令就会在 http://localhost:9000/ 中看到主页面。注意到,使用 webpack-dev-server 编译后的文件直接在内存中,不会输出到dist目录,但可以使用dist目录中的文件。

npx webpack-dev-server --config webpack.dev.js

dev-server官网 中其他的配置:

devServer: {
  clientLogLevel: 'warning', // 可能的值有 none, error, warning 或者 info(默认值)
  hot: true,  // 启用 webpack 的模块热替换特性, 这个需要配合: webpack.HotModuleReplacementPlugin插件
  contentBase:  path.join(__dirname, "dist"), // 告诉服务器从哪里提供内容, 默认情况下,将使用当前工作目录作为提供内容的目录
  compress: true, // 一切服务都启用gzip 压缩
  host: '0.0.0.0', // 指定使用一个 host。默认是 localhost。如果你希望服务器外部可访问 0.0.0.0
  port: 8080, // 端口
  open: true, // 是否打开浏览器
  overlay: {  // 出现错误或者警告的时候,是否覆盖页面线上错误消息。
    warnings: true,
    errors: true
  },
  publicPath: '/', // 此路径下的打包文件可在浏览器中访问。
  proxy: {  // 设置代理
    "/api": {  // 访问api开头的请求,会跳转到  下面的target配置
      target: "http://192.168.0.102:8080",
      pathRewrite: {"^/api" : "/mockjsdata/5/api"}
    }
  },
  quiet: true, // necessary for FriendlyErrorsPlugin. 启用 quiet 后,除了初始启动信息之外的任何内容都不会被打印到控制台。这也意味着来自 webpack 的错误或警告在控制台不可见。
  watchOptions: { // 监视文件相关的控制选项
    poll: true,   // webpack 使用文件系统(file system)获取文件改动的通知。在某些情况下,不会正常工作。例如,当使用 Network File System (NFS) 时。Vagrant 也有很多问题。在这些情况下,请使用轮询. poll: true。当然 poll也可以设置成毫秒数,比如:  poll: 1000
    ignored: /node_modules/, // 忽略监控的文件夹,正则
    aggregateTimeout: 300 // 默认值,当第一个文件更改,会在重新构建前增加延迟
  }
}

如果想要重温Webpack的基础配置可以从官网指南入手,想要了解更复杂灵活的功能可以添加各种插件,如果有兴趣自己写一个loader,送个666给你~

architecture-building-exterior-1029599.jpg

阅读 1.2k

前端工程化之路
只要投入时间,一切都很简单。

In me, past, present, future meet...

928 声望
68 粉丝
0 条评论

In me, past, present, future meet...

928 声望
68 粉丝
宣传栏