webpack整理

简介

webpack 强大的功能主要还是依赖于 loaderplugin 机制

基本使用

  • 全局安装:npm i webpack webpack-cli -g

  • 在项目目录下npm init -y

  • 在项目目录下新建public/目录

  • public/目录下新建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>Webpack Sample Project</title> </head> <body> <div id="root"></div> <script src="bundle.js"></script> </body> </html>
  • 在项目目录下新建app/目录
  • app/目录下新建main.js文件
const greeter = require('./greeter') document.querySelector('#root').appendChild(greeter())
  • app/目录下新建greeter.js文件
module.exports = function() { var greet = document.createElement('div') greet.textContent = 'Hi there and greetings!' return greet }
  • 在项目目录下新建webpack.config.js文件
const path = require('path') module.exports = { entry: './app/main.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'public') }, mode: 'production' }
  • 在命令行中,切换到项目目录下。输入webpack,即可在public/目录下生成bundle.js文件

基本配置

入口entry

一个 webpack 工程,首先至少要有一个入口

// 一个入口 module.exports = { entry: './index.js' } // 多个入口 module.exports = { entry: { pageA: './pageA.js', pageB: './pageB.js' } } // 也可以是数组(实际工程中很少使用这种方式) module.exports = { entry: ['./pageA.js', './pageB.js'] } // 或者 module.exports = { entry: { main: ['./pageA.js', './pageB.js'] } }

出口output

编译构建后,真正被浏览器加载的资源文件则是出口文件

出口的相关配置定义了输出文件的路径与文件名

path配置决定了最终打包后输出资源的文件路径,必须为系统绝对路径

// 单出口 module.exports = { output: { filename: 'output.js', // 文件名 path: __dirname + './dist' // 文件输出路径,必须为系统绝对路径 } } // 多个出口 - 对应多个入口 module.exports = { entry: { pageA: './a.js', pageB: './b.js' }, output: { // 会输出 /dist/pageA.js /dist/pageB.js // 占位符 [name] 为 chunkName。在此处则即是 entry 中多入口配置对象的key值,即pageA 与 pageB filename: '[name].js', path: __dirname + './dist' } }

如果使用了 html 插件,那 webpack 会智能的判断生成 htmlchunk 的相对路径,再以 script 方式引入

const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: { pageA: './a.js', pageB: './b.js' }, output: { filename: '[name].js', path: __dirname + '/dist', publicPath: 'https://xxx.com' // html 中引用的 js 资源从 pageA.js 变为 https://xxx.com/pageA.js }, plugins: [ new HtmlWebpackPlugin() ] }

模式mode

目前已有的模式:productiondevelopmentnone

默认:production

  • 设置为 productiondevelopment 时会同时默认设置 process.env.NODE_ENVproductiondevelopment
  • 设置 none 时,webpack 不做任何附加操作
module.exports = { // ... mode: 'development' }

devtool

devtool选项 配置结果
source-map 在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map,但是它会减慢打包速度
cheap-module-source-map 在一个单独的文件中生成一个不带列映射的map,不带列映射提高了打包速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便
eval-source-map 使用eval打包源文件模块,在同一个文件中生成干净的完整的source map。这个选项可以在不影响构建速度的前提下生成完整的sourcemap,但是对打包后输出的JS文件的执行具有性能和安全的隐患。在开发阶段这是一个非常好的选项,在生产阶段则一定不要启用这个选项
cheap-module-eval-source-map 这是在打包文件时最快的生成source map的方法,生成的Source Map 会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map选项具有相似的缺点

模块module

webpack 是一个模块打包器

webpack 自身不可能穷举处理所有的相关文件。于是就采用了 loader 方式

module 的主要配置项为 rules。这是一个数组配置,rules 中的每一项 rule 即配置了如何去处理一个模块

const path = require('path') module.exports = { // ... module: { rules: [ { // 正则,匹配的对象是 引用模块的绝对路径 test: /\.(js)$/, // use: 'babel-loader', // ps:babel 的配置我们更多是以 .babelrc 配置文件的方式存在项目根目录 use: { loader: 'babel-loader', options: { // 根据目标浏览器自动转换为相应 es5 代码 presets: ['@babel/preset-env'] } }, // 过滤 node_modules 目录 exclude: path.resolve(process.cwd(), './node_modules'), // 只匹配 src 目录 include: path.resolve(process.cwd(), './src') } ] } }

一个模块文件需要转化多次,需要多个 loader。比如一个 css 文件

先通过 css-loader 解释 css 文件内的 @importurl()
最后通过 style-loadercss<style> 标签插入至 dom

module.exports = { // ... module: { rules: [ { test: /\.(css|less)$/, use: [ 'style-loader', // 'css-loader', // 如果 css-loader 还需要增加配置 { loader: 'css-loader', options: { // 启用 sourceMap sourceMap: true } } ] } ] } }

解析resolve

这个配置决定如何去解析模块。比如:是否需要缓存此模块;是否自动识别扩展文件名等

module.exports = { // ... resolve: { // 别名 alias: { // import utils from '../../../utils' // 可以写成 import utils from '#/utils' '#': path.resolve(process.cwd(), './src') } } }

设置别名后,编辑器没有办法自动匹配文件路径了。

比如 vscode,可以设置 vscode/jsconfig.json文件

{ "compilerOptions": { "baseUrl": "./", "paths": { "#/*": ["src/*"] } } }

统计信息stats

  • 当我们执行 webpack 命令时,终端中会打印非常多的信息
  • 大部分配置都是默认开启,实际需求中只要关闭几个不必要的配置即可
  • 如果是开发环境,启动了webpack-dev-server,那需要 devServer 对象中配置

比如生产环境可以配置

module.exports = { // ... stats: { warnings: false, // 取消警告信息 children: false, // 取消子级信息 modules: false, // 取消模块构建信息 entrypoints: false// 不显示入口起点 } }

常用loader

loader 主要负责对于某些文件的处理与转化

babel-loader

将最新标准的代码转成当下浏览器可执行的 js 代码

主要是以 .babelrc 配置文件的方式存在项目根目录

css相关loaders

  • less-loader/sass-loaderless/sass预处理
  • postcss-loadercss样式后处理工具。css压缩、合并、自动兼容浏览器等功能利器
  • css-loader:解释css文件内的@importurl();可开启css-module
  • style-loader:将cssstyle标签插入dom
  • css-hot-loadercss热更新loader线上环境时勿加,会引起js文件contentHash每次都不同

file-loader

对于图片这样的静态资源,我们在代码中引入时,常以当前文件为基准,引入其相对路径下的图片。
而当我们访问 html 时,这个相对路径其实是基于 html 此时的路径的,故而会导致引入路径错误

file-loader 可以自动的识别 webpack 配置,打包资源图片,修复引入路径,进而保证资源引入正确。同时也支持修改输出后文件的路径与文件名、携带 hash 值等功能

url-loader

基本功能同 file-loader

在它基础上,可以设置一个 limit 配置项,意义为文件的体积大小,单位为字节。对于小于此大小的文件,会转化成 base64 的数据,替换 url 引入

对于小图片等资源常用这样的操作,好处是减少资源的请求次数;
或者在某些场景下,保证图片在 html 加载或渲染时就能展示,不需要再发起请求

常用plugin

plugin 是作用于 webpack 命令执行的整个生命周期的

html-webpack-plugin

只要是 web 应用的工程,必须要有 html 文件或者其他 html 模板文件

html-webpack-plugin主要功能就是可以根据项目中的 html 模板(没有也行),生成想要的 html 文件,并插入我们构建出来的 jscss 等资源

对于构建多页应用来说,每需要一个 html 文件,就需增加一个 html-webpack-plugin,并引入相应的 chunk

const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: { pageA: './a.js', pageB: './b.js' }, output: { filename: '[name].js', path: _dirname + '/dist' }, plugins: [ new HtmlWebpackPlugin({ filename: 'pageA.html', chunks: ['pageA'] }), new HtmlWebpackPlugin({ filename: 'pageB.html', chunks: ['pageB'] }) ] }

mini-css-extract-plugin

抽离 css 文件。不要以 style 标签的形式插入至 dom

如果引入了本插件,那么就需要在 css 文件的相关 loaders 中需要使用 MiniCssExtractPlugin.loader 替换 style-loader

const MiniCssExtractPlugin = require('mini-css-extract-plugin') module.exports = { // ... plugins: [ new MiniCssExtractPlugin({ // 生产环境需要hash可配置为 [name].[contenthash].css fileName: '[name].css' }) ], module: { rules: [ { test: /\.(css)$/, use: [ MiniCssExtractPlugin.loader, 'css-loader' ] } ] } }

HotModuleReplacementPlugin

模块热替换(HMR - Hot Module Replacement)功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面

  • 保留在完全重新加载页面时丢失的应用程序状态。
  • 只更新变更内容,以节省宝贵的开发时间。
  • 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。
  • webpack-dev-server 中需要设置 hottrue
  • style-loader 支持热替换,但是上小节中为了抽离 css 文件引入的 MiniCssExtractPlugin.loader 暂时未支持热替换,故而需要开发环境时采用 style-loader ,或者引入 css-hot-loader
  • react 工程需要实现组件热替换的话,需引入 react-hot-loadervue-loader 已经实现了HMR,无需要增加其他loader

uglifyjs-webpack-plugin

实现代码压缩、混淆、tree-shaking

webpack也已内置

commons-chunk-plugin

抽离多个入口chunk内的公共代码

webpack@4中,已使用 optimization.splitChunks 来实现这样的功能。其内部实现基于内置的 SplitChunksPlugin

webpack-bundle-analyzer

提供可视化的界面,以用来分析webpack构建后的打包情况

建议工程项目都可以添加,才能清晰掌握自己工程项目具体加载了哪些代码,进而做相应的打包优化

开发配置

webpack-dev-server主要就是启动了一个静态服务,让开发者可以方便的预览自己工程构建的webApp

  • host{string} 服务的host,默认为 localhost,如果有需要非本机访问的需求(如想通过手机访问页面),可配成 0.0.0.0
  • port{number} 服务端口号。不配置时,默认为8080,如若端口不可用,会自增寻找可用端口。但手动配置且端口号不可用时,不会自动寻找其他端口号
  • disableHostCheck{boolean} 默认为 false 。如果有绑定域名访问的需要的话,需设置为 true
  • allowedHosts{array} 可以自己配置允许的 hosts 白名单。将指定的域名添加至配置项中,即可通过该域名访问本服务
  • contentBase{boolean|string|array} 静态服务器的内容目录地址。或者说,就是访问服务器的ip/域名时,对应访问的工程里的文件夹
  • index{string} 主页文件名,默认index.html
  • hot{boolean} 启动 webpack 的模块热替换。即不需要刷新页面则更新代码。不过这需要插入热更新插件
  • historyApiFallback{boolean} 依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html
  • clientLogLevel{string} 默认为 info ,可选为 none, error, warning
  • stats:同统计信息[stats]
const path = require('path') module.exports = { // ... devServer: { // webpack-dev-server 访问的是内存中文件,此时文件并未真正输出到 dist 目录下 contentBase: path.join(__dirname, 'dist'), // contentBase: [path.join(__dirname, 'dist'), path.join(__dirname, 'www')], index: 'home.html' } }

构建配置

生产环境特殊的构建配置

尽量减小构建包的体积

optimization

webpack@4中尽量将这些插件内置,并将其配置化,主要收敛在 optimization 这个配置项中

  • minimize:当 mode 配置为 production 时,minimize 值为 true。若设为 false 时取消引入 uglifyjs-webpack-plugin
  • minimizer:配置执行代码压缩的工具,默认为 uglifyjs-webpack-plugin
  • splitChunks:即是 SplitChunkPlugin 的配置。用处是可以按一定规则将工程中代码提取一部分到一个新的 chunk 文件
  • runtimeChunkwebpack打包代码后,为了控制模块的依赖与加载,必不可少的需要往工程项目中加入webpack提供的相应执行代码。这部分代码会因模块的增删改变化而变化。故而可以通过配置runtimeChunk,将这部分运行时代码抽离出来。 runtimeChunk 默认为false,可以配置为 { name: 'manifest' } ,设为对象,指定name即可
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') const UglifyJsPlugin = require('uglifyjs-webpack-plugin') module.exports = { optimization: { minimizer: [ new UglifyJsPlugin({ cache: true, parallel: true, // 多进程压缩 sourceMap: true }), new OptimizeCSSAssetsPlugin({}) // 优化压缩css ] } }

splitChunks常见场景

  • 把不同页面公共的代码抽离到一个 commonChunk
  • 把外部依赖包单独抽离到一个 vendor
  • 把React/Vue 等项目必引的库单独抽离成 dll

splitChunks基本配置

  • name: 分离出的 chunk 名字,默认为truechunk name会自动生成。
  • maxInitialRequests: 最大可分割出来的 chunk 数。
  • maxAsyncRequests: 最大可分割出来的异步 chunk 数(按需加载时使用)。
  • cacheGroups: 这个配置项是分割代码的核心配置,这是一个对象,keychunk的唯一识别keyvaluechunk的具体分割规则配置,如下:
    • name: 分离出的chunk的名字,若未设置且 splitChunks.nametrue时,以chunkkeyname
    • minChunks: 抽离公共代码时,该公共代码最少被几个chunk引用了。
    • test: 模块匹配的规则
    • priority: 分离规则优先级。有时候一个模块可能被多个规则匹配到,设置优先级可以让某个规则分离的chunk具有更高匹配模块的优先级。

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: