Dev tool

devtool build rebuild production quality
(none) fastest fastest yes bundled code
eval fastest fastest no generated code
eval-cheap-source-map fast faster no transformed code (lines only)
eval-cheap-module-source-map slow faster no original source (lines only)
eval-source-map slowest fast no original source
cheap-source-map fast slow yes transformed code (lines only)
cheap-module-source-map slow slower yes original source (lines only)
inline-cheap-source-map fast slow no transformed code (lines only)
inline-cheap-module-source-map slow slower no original source (lines only)
inline-source-map slowest slowest no original source
source-map slowest slowest yes original source
hidden-source-map slowest slowest yes original source
nosources-source-map slowest slowest yes without source content

devtool的匹配规则 [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

eval表示每个模块都使用 eval() 执行,inline表示打包后合并到目标文件中,cheap表示只提示出错行数,不提示列数,module表示loader中的报错也会提示,source-map表示会生成map文件

source map:解决出错时,及时匹配到源代码报错位置

devServer

常用API

  • contentBase 目录地址
  • proxy 代理
  • open 编译后自动打开浏览器
  • hot 开启HMR
  • hotOnly 禁止刷新

WEBPACK-DEV-SERVER

  1. // package.json
  2. "script": {
  3. "server": "webpack-dev-server"
  4. }

仿webpack-dev-server

  1. const express = require('express');
  2. const webpackDevMiddleware = require('webpack-dev-middleware');
  3. const webpack = require('webpack');
  4. const config = require('./webpack.config.js');
  5. const complier = webpack(config);
  6. const app = express();
  7. app.use(webpackDevMiddleware(complier, {
  8. publicPath: config.output.publicPath,
  9. // do something...
  10. });
  11. app.listen(3000, () => {
  12. console.log('server is running');
  13. })

Tree Shaking

tree shaking通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)

  1. // 主要演示development环境下,production环境自带
  2. // package.json
  3. // sideEffects向compiler提供提示,表明项目中的哪些文件是 "pure(纯的 ES2015 模块)",由此可以安全地删除文件中未使用的部分。
  4. "sideEffects": false, // false代表所有模块都要检测
  5. "sideEffects": ['*.css'] // css后缀文件都不检测
  6. // webpack.config.js
  7. mode: 'development',
  8. optimization: {
  9. useExports: true //
  10. }

注意事项

  • 使用ES Module(即 importexport)
  • package.json文件中,添加sideEffects属性
  • modedevelopment时需要配置optimization: {useExports: true}开启,production环境下默认启用minification(代码压缩) 和 tree shaking

productiondevelopment配置整合

使用webpack-mergeproductiondevelopment共享配置进行单独整合。

  1. const merge = require('webpack-merge');
  2. module.exports = merge(commonCofig, devConfig/proConfig);

Code Splitting

代码分离

  1. // 同步代码加载
  2. // import的插件进行单独分离,挂载全局,再次访问时,直接读取缓存
  3. import _ from 'lodash';
  4. window._ = _;
  5. // 进行webpack配置,将单独分离出的vendor作为单独入口编译
  6. entry: {
  7. lodash: './src/lib/lodash.js',
  8. // other entry...
  9. }
  10. // 异步代码加载
  11. import(/*webpackChunkName: 'lodash'*/'lodash').then(({default: _}) => {
  12. // 业务code...
  13. });
  14. // 处理异步代码加载时,出现编译报错,需要babel支持@babel/plugin-syntax-dynamic-import

split Chunks

  1. // 使用webpack optimization的splitChunks配置
  2. optimization: {
  3. splitChunks: {
  4. // 代码分离的类型 all: 同异步代码 async: 异步代码 initial: 同步代码
  5. chunks: 'all' | 'async' | 'initial',
  6. // 代码大小最小30kb,才进行代码分离
  7. minSize: 30000,
  8. minRemainingSize: 0,
  9. maxSize: 0,
  10. // 代码引入最小次数,eg: 引入一次就会进行代码打包
  11. minChunks: 1,
  12. // 同时加载代码库数量
  13. maxAsyncRequests: 6,
  14. // 入口文件加载的库数量
  15. maxInitialRequests: 4,
  16. // 输出的文件名连接符
  17. automaticNameDelimiter: '~',
  18. cacheGroups: {
  19. defaultVendors: { // 组名
  20. // 检测从node_modules中引入,则分离到vendors组
  21. test: /[\\/]node_modules[\\/]/,
  22. // 进行匹配时的优先级
  23. priority: -10,
  24. // 输出的vendors组的文件名
  25. name: 'vendors.js',
  26. // 组成员代码类型
  27. chunks: 'all' | 'async' | 'initial'
  28. },
  29. // 上面分组条件都不符合,则归属于default组
  30. default: {
  31. minChunks: 2,
  32. priority: -20,
  33. // 如果已经被打包过,不再进行打包操作
  34. reuseExistingChunk: true
  35. }
  36. }
  37. }
  38. }

总结

  1. // 代码分割,与webpack无关
  2. /**
  3. webpack中实现代码分割两种方式:
  4. 第一种:同步代码:只需要webpack.config.js中配置optimization的splitChunks
  5. 第二种:异步代码(import):无需做任何配置,会自动代码分割
  6. */

Lazy Loading

  1. // 使用import进行异步加载,达到懒加载效果
  2. async function lazyLoading() {
  3. const {default: _} = await import('lodash');
  4. // other code...
  5. }

chunk

  1. // webpack中optimization配置的minChunks是表示被引用的最小次数 chunks为检测代码分离的类型,默认为异步
  2. chunks: 'async' | 'all' | 'initial',
  3. minChunks 2 // 被引用小于2次不会进行整合打包分组
  4. // output:chunkFilename
  5. output: {
  6. chunkFilename: '[name].chunk.js' // 打包后的文件直接引入chunk后的文件
  7. }

webpack analyse

  1. // webpack官方推荐的打包分析插件 webpack/analyse 在package.json中配置如下代码
  2. "script": { "dev": "webpack --profile --json > stats.json" }

preload prefetch

  1. // 在异步加载中添加注释 /*webpackPrefetch: true*/
  2. import(/*webpackPrefetch: true*/'lodash');

CSS代码分割

MiniCssExtractPlugin

开发环境中不适用,MiniCssExtractPlugin不支持HMR

  1. const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  2. module: {
  3. rules: [
  4. {
  5. test: /\.css$/,
  6. loader: MiniCssExtractPlugin.loader,
  7. // other code...
  8. }
  9. ]
  10. },
  11. // tree shaking 控制过滤文件
  12. optimization: {
  13. useExports: true
  14. },
  15. plugins: [new MiniCssExtractPlugin({
  16. filename: '[name].css',
  17. chunkFilename: '[name].chunk.css'
  18. })]
  19. // package.json
  20. "sideEffects": ["*.css"] // 不检测css后缀文件

optimize-css-assets-webpack-plugin

css代码合并压缩
optimization: { minimizer: [new OptimizeCssAssetsWebpackPlugin({})] }

webpack与浏览器缓存(Caching)

webpack打包编译成功后,浏览器进行访问,可以配置output: {filename: '[name].[contenthash].js', chunkFilename: '[name].[contenthash].js'},如果修改内容不涉及某些chunk,再次进行打包后,浏览器再次访问,会使用浏览器缓存。
webpack版本4之前可能会出现问题,主要是因为mainfest会发生变化,此时可以添加配置optimization:{runtimeChunk: {name:'runtime'}}

Shimming

webpack进行打包时,会出现不同兼容问题,比如引入的第三方库中有其他库的JQuery依赖,但是webpack是模块打包,不识别$,此时需要垫片shimming

webpack内置了ProvidePlugin,

  1. const webpack = require('webpack');
  2. plugins: [
  3. new webpack.ProvidePlugin({
  4. '$': 'jquery', // 识别文件中$,自动引入jquery
  5. '_join': ['lodash', 'join'] // 自动引入lodash中的join
  6. })
  7. ]

如何使模块中的this指向window

  1. // npm i imprts-loader -D
  2. module: {
  3. rules: [
  4. {
  5. test: /\.js$/,
  6. use: ['babel-loader', 'imports-laoder?this=>window']
  7. }
  8. ]
  9. }

环境变量

  1. // 添加环境变量,控制执行脚本时的配置
  2. "script": {
  3. "dev": "webpack --env.development --config webpack.dev.config.js",
  4. "prod": "webpack --env.production --cinfig webpack.prod.config.js"
  5. }
  6. // webpack.dev.config.js
  7. // 可以直接获取到env的值

Library打包

打包输出一个库时,使用者可能会通过不同方式引入,ES module CommonJS等方式,此时需要进行webpackoutput配置,libraryTarget: 'umd',意思为通用不同引入方式。但是不支持script标签引入,此时需要配置library: 'library',在全局增加一个library变量。libraryTarget的属性还可以为其他,比如window this global等,代表library挂载的位置。

自定义库中引入其他插件库

比如自定义库中引入lodash,但是使用者也引入lodash,这样会造成重复引入,增加项目体积,此时需要对webpack进行配置,externals:['lodash'],这样自定义库就不会引入lodash
externals的属性值类型可以为string array object function regex

  1. externals : {
  2. react: 'react'
  3. }
  4. // 或者
  5. externals : {
  6. lodash : {
  7. commonjs: "lodash",
  8. amd: "lodash",
  9. root: "_" // 指向全局变量
  10. }
  11. }
  12. // 或者
  13. externals : {
  14. subtract : {
  15. root: ["math", "subtract"]
  16. }
  17. }

发布NPM

通过webpack打包编译后,需要在package.json中修改main入口。
npm adduser —-> npm publish

PWA配置

针对上线的项目,需要使用PWA,即使服务器宕机,用户可以使用第一次访问时缓存的页面。
workbox-webpack-plugin插件可以进行相关需求配置。

  1. // webpack.config.js
  2. const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
  3. plugins: [
  4. new WrokboxWebpackPlugin.GenerateSW({
  5. clientsClaim: true,
  6. skipWaiting: true
  7. })
  8. ]
  9. // 业务代码
  10. if('serviceWorker' in navigator) {
  11. window.addEventListener('load', () => {
  12. navigator.serviceWorker.register('打包编译后的service文件名').then(registration =>{
  13. // success coding...
  14. }).catch(error => {
  15. // fail coding...
  16. })
  17. })
  18. }

TypeScript配置

javascript的超集,需要在webpack.config.js中进行配置,额外需要在根目录添加tsconfig.json

  1. // webpack.config.js
  2. module: {
  3. rules: [
  4. { test: /\.tsx?$/, loader: 'ts-loader', exclude: /node_modules/ }
  5. ]
  6. }
  7. // tsconfig.json
  8. {
  9. "compilerOptions": {
  10. "outDir": "./dist",
  11. "module": "es6",
  12. "target": "es5",
  13. "allowJs": true
  14. }
  15. }

注意⚠️:调用第三方库时,不会进行报错,这时需要安装types/lodash等类似的约束。

WebpackDevServer请求转发

本地跨域,使用devServer中的proxy进行配置。

  1. devServer: {
  2. proxy: {
  3. '/api': {
  4. target: 'http://deankwan.com', // 首先请求目标路径
  5. secure: true, // 可以对https请求进行转发
  6. // 拦截
  7. bypass: function(req, res, proxyOptions) {
  8. if (req.headers.accept.indexOf('html') !== -1) {
  9. return '/index.html';
  10. }
  11. },
  12. pathRewrite: {
  13. 'productList.json': '' // 所有请求路径为productList都清空
  14. },
  15. changeOrigin: true, // 改变origin
  16. }
  17. }
  18. }
  19. // 更多知识请详情见webpack官网

WebpackDevServer解决单页面应用路由问题

historyApiFallback: true historyApiFallback的配置项详情见webpack官网

EsLint配置

安装 npm i eslint -D 初始化 npx eslint --init

webpack打包优化

  • 技术迭代( Node Npm Yarn ),更新最新依赖
  • 尽可能少的模块上应用Loader
  • Plugin尽可能精简并确保可靠性
  • resolve参数合理配置,比如引入文件不写扩展名,使用extensions进行省略补写,如果配置的过多,打包时会按照扩展名进行遍历,alias可以对路径做别名,这样可以更快的定位。
  • 第三方引入库一般不会发生变化,所以可以单独进行打包,之后再打包就不需要重复打包。DLLPlugin ```javascript // webpack.dll.js 主要作用于对引入类库的打包,并生成映射json文件 module.exports = { entry: {
    1. vendors: ['react', 'react-dom', 'lodash']
    }, output: {
    1. filename: '[name].dll.js',
    path: path.resolve(__dirname, ‘../dll’), // 打包输出在dll文件夹下 library: ‘[name]’, // 挂载在全局上,向外暴漏库引用 }, plugin: [ // 使用webpack内置插件,对生成的库与对应路径下的json文件进行映射。
    1. new webpack.DllPlugin({
    2. name: '[name]',
    3. path: path.resolve(__dirname, '../dll/[name].manifest.json')
    }) ] }

// webpack.prod.js 生产环境中,对映射文件进行查找。 // add-asset-html-webpack-plugin 在html挂载静态文件的插件 new AddAssetHtmlWebpackPlugin({ filepath: path.resolve(dirname, ‘../dll/vendors.dll.js’) }), // 打包时进行关联查询,如果找到对应的manifest文件,不会再进行打包编译 new webpack.DllReferencePlugin({ manifest: path.resolve(dirname, ‘../dll/vendors.manifest.json’) })

// 业务情况: 如果对类库进行分类,打包多个manifest const plugins = []; // 读取dll文件夹下的文件 const files = fs.readdirSync(path.resolve(dirname, ‘../dll’)); files.forEach(file => { // 针对dll.js结尾的文件,挂载到html上 if(/.*.dll.js/.test(file)) { plugins.push(new AddAssetHtmlWebpackPlugin({ filepath: path.resolve(dirname, ‘../dll’, file) })) }

// manifest.json 映射对应文件 if(/.*.manifest.json/.test(file)) { plugins.push(new webpack.DllReferencePlugin({ manifest: path.resolve(__dirname, ‘../dll’, file) })) } }); ```

  • 控制包文件大小,可以使用tree shaking,但是尽量避免不必要的插件和类库的引入。
  • webpack是通过node进行的,所以是单线程的。可以通过node中的多进程进行打包thread-loader parallel-webpack happypackparallel-webpack是针对多个页面进行打包的,不需要等一个页面打包完,再打包另一个。
  • 合理使用sourceMap
  • 结合stats分析打包结果,通过线上或者本地的打包分析工具进行分析,针对性的进行优化。
  • 开发环境内存编译。在使用devServer进行运行项目时,项目编译后不会放在dist文件夹下,而是放在了内存中进行读取。
  • 开发环境无用插件的剔除。