image.png

基于webpack4

功能

  • 编译,包括JavaScript的编译、css的编译
  • 文件的压缩、打包、合并、公共模块提取等
  • 图片等资源的处理,如压缩、合并雪碧图等
  • Tree-shaking等优化JavaScript工具
  • Webpack-dev-serverEslint、热更新等帮助开发的工具

打包

webpack

直接以当前目录下的名为 webpack.config.js 的文件名作为配置文件进行打包

webpack —config configfile

指定一个文件作为配置文件进行打包

  1. webpack --config ./config/webpack.config.js --mode production

核心

entry、output

dev-server只能配置[hash].js
Cannot use [chunkhash] or [contenthash] for chunk in ‘js/[name].[contenthash:6].js’ (use [hash] instead)

  1. module.exports = {
  2. /**
  3. * 入口文件,如果不做任何配置默认入口是[src/index.js]
  4. */
  5. // entry: [path.resolve(__dirname, '../src/app.js')],
  6. // 多入口需要如下键值对形式
  7. entry: {
  8. app: path.resolve(__dirname, '../src/app.js'),
  9. },
  10. /*--------------*/
  11. /**
  12. * 出口文件
  13. */
  14. output: {
  15. // 打包后的路径
  16. path: path.resolve(__dirname, '../dist'),
  17. // 打包后的文件名,默认打包出来是main.js
  18. filename: 'js/[name].[hash:6].min.js',
  19. // publicPath: 'https://cloud-app.com.cn/app/',
  20. },
  21. }

entry

entrywebpack的入口文件,默认是src/index.js

  • 单入口

数组形式定义

  1. // 单入口形式
  2. entry: [path.resolve(__dirname, '../src/app.js')],
  • 多入口

键值对来定义

  1. // 多入口需要如下键值对形式
  2. entry: {
  3. app: path.resolve(__dirname, '../src/app.js'),
  4. },

output

outputwebpack的打包出口,默认结果是main.js,最终的打包结果会根据output的定义输出,会影响到资源的 路径。

  1. /**
  2. * 出口文件
  3. */
  4. output: {
  5. // 打包后的路径
  6. path: path.resolve(__dirname, '../dist'),
  7. // 打包后的文件名,默认打包出来是main.js
  8. filename: 'js/[name].[contenthash:6].min.js',
  9. },

loader

loader是webpack的编译方法

  • webpack自身只能处理JavaScript,所以对别的资源处理需要loader
  • webpack只负责打包,相关的编译操作,需要loader
  • loader本质是一个方法,使用时大多需要额外安装
  1. module: {
  2. rules: [
  3. {
  4. test: /\.js$/,
  5. use: {
  6. loader: 'babel-loader',
  7. },
  8. }
  9. ]
  10. }

常见loader

  • 处理css

style-loader、css-loader

  • 处理图片、字体等资源

url-loader、image-loader

  • 编译loader

less-loader、sass-loader、babel-loader

  • 语法糖loader

vue-loader

plugins

对于打包之后的结果单额一些处理

externals

定义不去做打包的模块

  1. externals: {
  2. 'AMap': 'AMap',
  3. },

基本使用

处理JS

安装babel

  1. npm install @babel/core @babel/preset-env --save-dev

安装babel-loader

  1. npm install babel-loader --save-dev

babel-preset

配置了**presets**才能够准确编译es6+

presets是存储JavaScript不同标准的插件,通过使用正确的presets,告诉babel按照哪个规则编译

常见规范:

  • es2015
  • es2016
  • es2017
  • env
  • babel-preset-stage

babel-preset的target配置

target是preset的核心配置,告诉preset编译的具体目标;

Target的值:

  • 以browsers为目标【通常使用】
  • 以node的版本为目标
  • 以特定的浏览器为目标

loader完整配置

  1. rules: [
  2. {
  3. test: /\.js$/, // 检测js
  4. use: {
  5. loader: 'babel-loader', // 使用babel-loader
  6. // 打包参数
  7. options: {
  8. // 存储JavaScript不同标准的插件
  9. presets: [
  10. ['@babel/preset-env', {
  11. targets: {
  12. // 需要适配的浏览器类型
  13. browsers: ["> 1%", "last 2 versions", "not ie <= 8", "iOS 8"]
  14. }
  15. }]
  16. ]
  17. }
  18. }
  19. }
  20. ]

ES6方法的编译

比如Promise的编译,以上配置是不能够去编译的。

全局生效
  1. npm install babel-polyfill --save-dev

通过babel-polyfill产生全局对象,包含es6-es5的重写,从而将es6语法进行编译。

使用
  • 代码中引用 ```javascript import ‘babel-polyfill’;

new Promise(setTimeout(() => { const typescript = ‘typescript’; console.log(typescript); }, 300))

  1. - webpack配置
  2. ```javascript
  3. // 默认打包后入口文件为 main.js
  4. entry: ['babel-polyfill', path.resolve(__dirname, '../src/app.js')]
  5. // 多页面配置写法,打包后入口文件为app.js
  6. entry: {
  7. app: ['babel-polyfill', path.resolve(__dirname, '../src/app.js')],
  8. }

局部生效

只对使用到的方法进行编译,适用于框架的开发

  1. npm install @babel/plugin-transform-runtime @babel/runtime --save-dev

image.png

提取babel-loader options

添加 .babelrc 文件,添加以下内容。

  1. {
  2. // 存储JavaScript不同标准的插件
  3. "presets": [
  4. ["@babel/preset-env", {
  5. "modules": false, // 保留es6的模块化语法
  6. "targets": {
  7. // 需要适配的浏览器类型
  8. "browsers": ["> 1%", "last 2 versions", "not ie <= 8", "iOS 8"]
  9. }
  10. }]
  11. ],
  12. "plugins": [
  13. "@babel/transform-runtime"
  14. ]
  15. }

TypeScript配置

安装loader

  1. npm install typescript ts-loader --save-dev

添加loader

  1. {
  2. test: /\.tsx?$/, // 检测ts或者tsx文件
  3. use: {
  4. loader: 'ts-loader',
  5. options: {
  6. transpileOnly: true
  7. }
  8. },
  9. }

配置文件

添加tsconfig.json
image.png

语法糖配置

image.png

HTML模板

通过为index.html创建HTML模板,webpack可以自动将打包好的js文件添加到index.html中。

安装webpack-html-plugin

  1. npm install --save-dev html-webpack-plugin

修改webpack.config.js,添加plugin

  1. // 自动创建 HTML 模板供 Webpack 打包结果使用,包括文件名称 模板参数 meta 标签配置 压缩等等。SPA 与 MPA 都会使用到。
  2. const HtmlWebpackPlugin = require('html-webpack-plugin');
  3. new HtmlWebpackPlugin({
  4. title: 'WebPack',
  5. template: path.resolve(__dirname, "../public/index.html"),
  6. filename: "index.html",
  7. inject: true, // 是否自动引入资源
  8. icon: path.join(__dirname, "../public/favicon.ico"),
  9. minify: _DEV_ ? false : {
  10. // collapseWhitespace: true,
  11. // collapseBooleanAttributes: true,
  12. // collapseInlineTagWhitespace: true,
  13. removeComments: true,
  14. removeRedundantAttributes: true,
  15. removeScriptTypeAttributes: true,
  16. removeStyleLinkTypeAttributes: true,
  17. minifyCSS: true,
  18. minifyJS: true,
  19. minifyURLs: true,
  20. useShortDoctype: true,
  21. }
  22. }),

处理CSS

css编译配置

处理css主要有以下loader:

  • style-loader:负责将css自动添加到html文件中
  • css-loader:负责处理对css文件的依赖
  • postcss-loader:负责补齐css浏览器前缀

安装loader

  1. npm install css-loader style-loader postcss-loader --save-dev

配置loader

  1. {
  2. // 从下往下编译的,所以css-loader在下
  3. test: /\.css$/,
  4. use: [
  5. {
  6. loader: 'style-loader',
  7. },
  8. {
  9. loader: 'css-loader',
  10. },
  11. // css兼容性处理,添加前缀
  12. {
  13. loader: 'postcss-loader',
  14. options: {
  15. plugins: function () {
  16. return [
  17. require('precss'),
  18. require('autoprefixer')
  19. ];
  20. }
  21. }
  22. },
  23. ]
  24. },

需要添加postcss.config.js

  1. module.exports = {
  2. plugins: [
  3. require("precss")(),
  4. require('autoprefixer')()
  5. ]
  6. };

以及在package.json添加

  1. "browserslist": [
  2. "Android 2.3",
  3. "Android >= 4",
  4. "Chrome >= 20",
  5. "Firefox >= 19",
  6. "Explorer >= 8",
  7. "iOS >= 6",
  8. "Opera >= 12",
  9. "Safari >= 6"
  10. ]

less编译器

安装

  1. npm install less less-loader --save-dev

添加loader

在上面css-loader的基础上添加less-loader即可。

  1. {
  2. test: /\.less/,
  3. use: [
  4. /**
  5. * MiniCssExtractPlugin提取css为一个文件,MiniCssExtractPlugin没有hdr,
  6. * 所以开发使用style-loader
  7. */
  8. devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
  9. // 'style-loader', // 将css文件打包到js
  10. 'css-loader', // css文件处理
  11. // css兼容性处理,添加前缀
  12. {
  13. loader: 'postcss-loader',
  14. options: {
  15. plugins: function () {
  16. return [
  17. require('precss'),
  18. require('autoprefixer')
  19. ];
  20. }
  21. }
  22. },
  23. 'less-loader', // less编译
  24. ]
  25. }

scss编译器

sass和scss只是语法不同

安装
  1. npm install node-sass sass-loader --save-dev

添加loader

less-loader的配置换成sass的即可

  1. {
  2. test: /\.scss/,
  3. use: [
  4. /**
  5. * MiniCssExtractPlugin提取css为一个文件,MiniCssExtractPlugin没有hdr,
  6. * 所以开发使用style-loader
  7. */
  8. devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
  9. // 'style-loader', // 将css文件打包到js
  10. 'css-loader', // css文件处理
  11. // css兼容性处理,添加前缀
  12. {
  13. loader: 'postcss-loader',
  14. options: {
  15. plugins: function () {
  16. return [
  17. require('precss'),
  18. require('autoprefixer')
  19. ];
  20. }
  21. }
  22. },
  23. 'sass-loader', // less编译
  24. ]
  25. }

css文件提取

开发环境推荐使用style-loader,因为extract-text-webpack-plugin无法使用热替换hmr功能

安装插件

Since webpack v4 the **extract-text-webpack-plugin** should not be used for css.
Use mini-css-extract-plugin instead.
原本使用的 extract-text-webpack-plugin 不好用了,使用mini-css-extract-plugin代替

  1. npm install mini-css-extract-plugin webpack --sadev-dev

loader改造
  1. // css打包提取为单独文件
  2. const MiniCssExtractPlugin = require("mini-css-extract-plugin");
  3. // loader改造
  4. {
  5. test: /\.scss$/,
  6. use: [
  7. MiniCssExtractPlugin.loader,
  8. "css-loader",
  9. "postcss-loader",
  10. "sass-loader",
  11. ],
  12. }

添加plugin

MiniCssExtractPlugin必须要添加到plugin

  1. // 添加plugin
  2. new MiniCssExtractPlugin({
  3. filename: '[name].[contenthash:8].css'
  4. }),

处理图片

主要有以下loader用于处理图片:

  • file-loader:用于将图片转为链接
  • url-loader:封装file-loader,对小图片直接Base64编码,大图片通过file-loader进行处理
  • image-webpack-loader:封装图片压缩loader,对各种图片进行压缩

file-loader

处理图片

  1. {
  2. test: /\.(png|jpe?g|gif|svg|bmp|mp4)$/,
  3. use: [
  4. {
  5. loader: 'file-loader',
  6. options: {
  7. name: '/imgs/[name].[hash:4].[ext]'
  8. }
  9. }
  10. ]
  11. },

url-loader

封装原有file-loader,转换小图片为base64
小于 limit 的文件会被转为 base64,单位为bite,
大于 limit 的使用 file-loader 进行处理,单独打包

  1. {
  2. test: /\.(png|jpe?g|gif|svg|bmp|mp4)$/,
  3. use: [
  4. {
  5. loader: 'url-loader',
  6. options: {
  7. outputPath: 'img/',
  8. // 压缩之后的图片如果小于10KB,那么将直接转为Base64编码,否则通过URL的形式连接图片;
  9. limit: 10 * 1024, // 默认转为Base64编码
  10. name: '[name].[contenthash:6].[ext]',
  11. }
  12. }
  13. ]
  14. },

图片压缩

安装loader

去除之前的一些压缩loader,使用image-webpack-loader,其实就是一个二次封装
这里的image-webpack-loader需要<=6.0.6,不然也会不成功,因为image-webpack-loader越高,所依赖的插件库也就越高。
image-webpack-loader/v/6.0.0

  1. npm uni img-loader imagemin imagemin-pngquant imagemin-mozjpeg
  2. npm i image-webpack-loader@6.0.0 --save-dev
  1. {
  2. test: /\.(png|jpe?g|gif|svg|bmp|mp4)$/,
  3. use: [
  4. {
  5. loader: 'url-loader',
  6. options: {
  7. outputPath: 'img/',
  8. publicPath: '../img/',
  9. // base64配置 小于 limit 字节的文件会被转为 base64,大于 limit 的使用 file-loader 进行处理,单独打包
  10. limit: 8000,
  11. name: '[name].[hash:4].[ext]'
  12. }
  13. },
  14. /*********** loader for zip img ***************/
  15. {
  16. loader: 'image-webpack-loader',
  17. options: {
  18. mozjpeg: {
  19. progressive: true,
  20. quality: 65
  21. },
  22. // optipng.enabled: false will disable optipng
  23. optipng: {
  24. enabled: false,
  25. },
  26. pngquant: {
  27. quality: [0.65, 0.90],
  28. speed: 4, // 1-11 越小压缩效果越好
  29. },
  30. gifsicle: {
  31. interlaced: false,
  32. },
  33. // the webp option will enable WEBP
  34. webp: {
  35. quality: 75
  36. }
  37. }
  38. },
  39. /*********** loader for zip img ***************/
  40. ]
  41. },

其实image-webpack-loader就是对以下的loader做了一个封装。
当前6.0.0对应以下版本loader

  1. "dependencies": {
  2. "imagemin": "^7.0.0",
  3. "imagemin-gifsicle": "^6.0.1",
  4. "imagemin-mozjpeg": "^8.0.0",
  5. "imagemin-optipng": "^7.0.0",
  6. "imagemin-pngquant": "^8.0.0",
  7. "imagemin-svgo": "^7.0.0",
  8. "imagemin-webp": "^5.1.0",
  9. }

雪碧图

1、postcss-sprites

属于postcss-loader的插件,会自动把css文件中引入的背景图合成雪碧图,并修改css文件。

安装

  1. npm i postcss-loader postcss-sprites --save-dev

配置

a、直接修改cssloader

  1. {
  2. test: /\.css$/,
  3. use: [
  4. MiniCssExtractPlugin.loader,
  5. "css-loader",
  6. /*********** loader for sprites ***************/
  7. {
  8. loader: 'postcss-loader',
  9. options: {
  10. ident: 'postcss',
  11. plugins: [require('postcss-sprites')(spritesConfig)]
  12. }
  13. }
  14. /*********************************************/
  15. ],
  16. },

b、由于之前已经添加过postcss-loader,直接修改配置也可以

  1. /**
  2. * @Author: forguo
  3. * @Date: 2021/12/19 21:49
  4. * @Description: postcss.config
  5. */
  6. /*********** sprites config ***************/
  7. let spritesConfig = {
  8. spritePath: './dist/img'
  9. }
  10. /******************************************/
  11. module.exports = {
  12. ident: 'postcss',
  13. plugins: [
  14. require("precss")(),
  15. require('autoprefixer')(),
  16. /*********** loader for sprites ***************/
  17. require('postcss-sprites')(spritesConfig)
  18. /*********************************************/
  19. ]
  20. };

[注]:生成雪碧图之后,图片体积响应也会变大,也不会再去压缩或者base64转码,需要合理使用。

2、webpack-spritesmith

独立插件,会按照指定的路径的指定图片,生成一个雪碧图,
和一个雪碧图相关的css,不会修改原css。

压缩前后代码对比
image.png

安装

  1. npm i webpack-spritesmith --save-dev

配置

  1. // css雪碧图插件
  2. // 【问题】没有将雪碧图打包进css,而且 会被CleanWebpackPlugin删除掉雪碧图文件夹
  3. new SpritesmithPlugin({
  4. // 原图片路径
  5. src: {
  6. cwd: path.resolve(__dirname, '../src/sprites'),
  7. glob: '*.png'
  8. },
  9. // 生成雪碧图及css路径
  10. target: {
  11. image: path.resolve(__dirname, '../dist/sprites/sprite.[hash:6].png'),
  12. css: path.resolve(__dirname, '../dist/sprites/sprite.[hash:6].css')
  13. },
  14. // css引入雪碧图
  15. apiOptions: {
  16. cssImageRef: '../sprites/sprite.[hash:6].png',
  17. },
  18. // 配置spritesmith选项,非必选
  19. spritesmithOptions: {
  20. algorithm: `top-down`,//設定圖示的排列方式
  21. padding: 4 //每張小圖的補白,避免雪碧圖中邊界部分的bug
  22. }
  23. }),

字体处理

  1. {
  2. test: /\.(ttf|eot|woff2?)$/,
  3. loader: 'file-loader',
  4. options: {
  5. outputPath: 'fonts/',
  6. publicPath: '../fonts/',
  7. name: '[name].[hash:4].[ext]'
  8. },
  9. },

清理输出文件夹

每次build之前,将上次的打包目录清楚

安装clean-webpack-plugin

  1. npm install --save-dev html-webpack-plugin

修改webpack.config.js,添加 plugin

  1. plugins: [
  2. // 清除上次打包的代码
  3. new CleanWebpackPlugin(),
  4. // 默认会压缩html,
  5. new HtmlWebpackPlugin({
  6. title: 'app',
  7. template: path.resolve(__dirname, '../public/index.html'),
  8. filename: 'index.html',
  9. inject: true,
  10. minify: false,
  11. }),
  12. ]

Webpack的环境系统

image.png

开发环境和生产环境的区别

development

  • 去除无用代码
  • 图片压缩,转码base64,雪碧图
  • 提取公共代码

    production

  • webpack-dev-server

  • source-map
  • 代码风格检查

不用环境下的配置编写

环境区分

  1. webpack --env env-name

在common配置中读取env参数

配置文件目录

image.png

common通过env参数,合并对应的环境配置

  1. // merge配置合并
  2. const { merge } = require('webpack-merge');
  3. // dev配置
  4. const devConfig = require('./webpack.dev');
  5. // prod配置
  6. const prodConfig = require('./webpack.prod');
  7. module.exports = env => {
  8. console.log(chalk.blue('Environment:'), chalk.yellowBright(env));
  9. console.log(chalk.blue('Version:'), chalk.yellowBright(version));
  10. // 是否是开发环境
  11. const _DEV_ = env === 'development';
  12. const commonConfig = {
  13. // 默认打包出来是main.js
  14. // entry: ['babel-polyfill', path.resolve(__dirname, '../src/app.js')],
  15. entry: {
  16. app: ['babel-polyfill', path.resolve(__dirname, '../src/app.js')]
  17. },
  18. output: {
  19. // path: path.resolve(__dirname, '../dist'),
  20. filename: "js/[name].[contenthash:8].js",
  21. // publicPath: '/',
  22. },
  23. module: {
  24. rules: [
  25. {
  26. test: /\.js$/, // 检测js文件
  27. use: {
  28. loader: "babel-loader", // 使用babel-loader
  29. }
  30. },
  31. {
  32. // https://www.dengwb.com/typescript/project/compile-tools.html#ts-loader
  33. test: /\.tsx?$/, // 检测ts或者tsx文件
  34. use: {
  35. loader: 'ts-loader',
  36. options: {
  37. // 忽略类型检查,提高编译速度
  38. transpileOnly: true
  39. }
  40. },
  41. },
  42. {
  43. test: /\.css$/,
  44. use: [
  45. MiniCssExtractPlugin.loader,
  46. "css-loader",
  47. "postcss-loader",
  48. ],
  49. },
  50. {
  51. test: /\.less$/,
  52. use: [
  53. MiniCssExtractPlugin.loader,
  54. "css-loader",
  55. "postcss-loader",
  56. "less-loader",
  57. ],
  58. },
  59. {
  60. test: /\.scss$/,
  61. use: [
  62. MiniCssExtractPlugin.loader,
  63. "css-loader",
  64. "postcss-loader",
  65. "sass-loader",
  66. ],
  67. }
  68. ]
  69. },
  70. plugins: [
  71. new HtmlWebpackPlugin({
  72. title: 'WebPack',
  73. template: path.resolve(__dirname, "../public/index.html"),
  74. filename: "index.html",
  75. inject: true, // 是否自动引入资源
  76. icon: path.join(__dirname, "../public/favicon.ico"),
  77. minify: _DEV_ ? false : {
  78. // collapseWhitespace: true,
  79. // collapseBooleanAttributes: true,
  80. // collapseInlineTagWhitespace: true,
  81. removeComments: true,
  82. removeRedundantAttributes: true,
  83. removeScriptTypeAttributes: true,
  84. removeStyleLinkTypeAttributes: true,
  85. minifyCSS: true,
  86. minifyJS: true,
  87. minifyURLs: true,
  88. useShortDoctype: true,
  89. }
  90. }),
  91. new CleanWebpackPlugin(), // outputPath
  92. new MiniCssExtractPlugin({
  93. filename: 'css/[name].[contenthash:7].css'
  94. }),
  95. new WebpackBar({
  96. name: name || 'WebPack',
  97. color: '#61dafb', // react 蓝
  98. }),
  99. ]
  100. }
  101. return merge(commonConfig, {
  102. development: devConfig,
  103. production: prodConfig
  104. }[env])
  105. }

在package.json中通过指定mode来区分环境

  1. "scripts": {
  2. "dev": "npm run start",
  3. "start": "webpack-dev-server --env development --config ./config/webpack.common.js",
  4. "build": "webpack --env production --config ./config/webpack.common.js"
  5. },

webpack4的环境区分

通过将 mode 参数设置为 development、production 或 none,
可以启用 webpack 与每个环境对应的内置优化。默认值为生产。

  • 写法1

    1. webpack --mode production/development
  • 写法2 ```javascript module.exports = { mode: ‘development’, // or production }

  1. <a name="jOcBT"></a>
  2. ## Webpack-dev-server的使用
  3. > 可以模拟线上环境进行开发调试的服务工具
  4. <a name="vXih4"></a>
  5. ### 额外功能
  6. - 1、路径重定向
  7. - 2、浏览器中显示编译错误
  8. - 3、接口代理【跨域】
  9. - 4、热更新
  10. <a name="TvrGp"></a>
  11. ### 使用
  12. <a name="DFTbj"></a>
  13. #### 安装webpack-dev-server
  14. ```javascript
  15. npm install webpack-dev-server --save-dev

devServer常用配置

  • inline:服务的开启模式
  • lazy:懒编译
  • port:代理端口
  • overlay:错误遮罩
  • historyApiFallback:路径重定向
  • proxy:代理请求
  • Hot:热更新 live reloading

代理接口

  1. proxy: {
  2. // 需要代理的地址
  3. '/api/*': {
  4. // 代理的目标地址
  5. target: domain[env].api,
  6. changeOrigin: true,
  7. pathRewrite: {
  8. '^/api': '/'
  9. },
  10. }
  11. }

配置

webpack.dev.config中添加devServer的配置

  1. devServer: {
  2. open: 'Google Chrome', // 可以使用Boolean或者指定浏览器
  3. port: 10086, // 服务端口
  4. hot: true, // 热更新
  5. host: '0.0.0.0', // 服务地址
  6. noInfo: true, // 禁止显示诸如 Webpack 捆绑包信息之类的消息
  7. historyApiFallback: true, // 路径重定向
  8. /**
  9. * 设置代理配置【跨域】
  10. */
  11. proxy: {
  12. // 需要代理的地址
  13. '/api/*': {
  14. // 代理的目标地址
  15. target: domain[env].api,
  16. changeOrigin: true,
  17. pathRewrite: {
  18. '^/api': '/'
  19. },
  20. }
  21. }
  22. }

开启服务

package.json中添加自定义命令 npm run dev

  1. "scripts": {
  2. "dev": "npm run start",
  3. "start": "webpack-dev-server --env development --config ./config/webpack.common.js",
  4. },

source-map的使用

模式

image.png

详解

image.png

Webpack原理分析

1、依赖于Node环境与文件操作系统
2、打包过程,利用Node去读取文件,然后进行依稀字符串处理,再利用Node去写入文件。

打包流程解析

  • 读取配置文件
  • 注册内部插件与配置插件
  • Loader编译
  • 组织模块
  • 生成最终文件,导出

Loader原理解析

创建loader

新建ws-loader/index.js

  1. /**
  2. * @Author: forguo
  3. * @Date: 2021/12/11 15:13
  4. * @Description: 一个自定义的loader
  5. */
  6. module.exports = function (resource) {
  7. /**
  8. * 将$xx转换成 wei-xxx
  9. * @type {RegExp}
  10. */
  11. const reg = /\$\(/g;
  12. try {
  13. return resource.replace(reg, 'wei-').replace(')', '');
  14. } catch (e) {
  15. console.log('ws-loader-error', e);
  16. }
  17. }

使用loader

和正常loader一样,检测文件名并使用即可。

  1. {
  2. test: /\.ws$/, // 检测ws文件
  3. use: {
  4. loader: "./ws-loader/index", // 使用babel-loader
  5. }
  6. },

打包结果分析

  1. (function (modules) {
  2. // module缓存
  3. var installedModules = {};
  4. function __webpack_require__(moduleId) {
  5. /******/
  6. /******/ // Check if module is in cache
  7. /******/
  8. if (installedModules[moduleId]) {
  9. /******/
  10. return installedModules[moduleId].exports;
  11. /******/
  12. }
  13. /******/ // Create a new module (and put it into the cache)
  14. /******/
  15. var module = installedModules[moduleId] = {
  16. /******/ i: moduleId,
  17. /******/ l: false,
  18. /******/ exports: {}
  19. /******/
  20. };
  21. /******/
  22. /******/ // Execute the module function
  23. /******/
  24. modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  25. /******/
  26. /******/ // Return the exports of the module
  27. /******/
  28. return module.exports;
  29. }
  30. // Load entry module and return exports
  31. return __webpack_require__(__webpack_require__.s = 0);
  32. })({
  33. // 入库文件
  34. "./src/app.js": (function (module, __webpack_exports__, __webpack_require__) {
  35. // 调用less
  36. __webpack_require__("./src/css/app.less");
  37. }),
  38. // less处理
  39. "./src/css/app.less": (function (module, __webpack_exports__, __webpack_require__) {
  40. __webpack_require__.r(__webpack_exports__);
  41. // extracted by mini-css-extract-plugin
  42. }),
  43. // 多入口文件app.js
  44. 0:(function(module, exports, __webpack_require__) {
  45. // 调用app.js
  46. eval("__webpack_require__(/*! babel-polyfill */\"./node_modules/babel-polyfill/lib/index.js\");\n" +
  47. "module.exports = __webpack_require__(\"./src/app.js\");");
  48. })
  49. })

Dev-server原理

Express+webpack-dev-middleware中间件开启服务,开启的服务执行打包出来额代码。

Webpack优化

目的:

  • 减少加载代码大小
  • 提取公共资源,减少加载次数

1、webpack-cdn-plugin的使用

好处:减少打包体积,利用CDN提高访问速度

  1. // CDN提取
  2. const WebpackCdnPlugin = require('webpack-cdn-plugin');
  3. const cdnLoader = (prod = false) => {
  4. return {
  5. modules: [
  6. {
  7. name: 'axios',
  8. var: 'axios',
  9. path: 'axios.min.js'
  10. },
  11. {
  12. name: 'vue',
  13. var: 'Vue',
  14. path: 'vue.runtime.min.js'
  15. },
  16. {
  17. name: 'vue-router',
  18. var: 'VueRouter',
  19. path: 'vue-router.min.js'
  20. }
  21. // {
  22. // name: 'view-design',
  23. // var: 'iview',
  24. // path: 'iview.min.js'
  25. // }
  26. ],
  27. prod,
  28. publicPath: '/node_modules',
  29. prodUrl: '//cdn.staticfile.org/:name/:version/:path'
  30. // prodUrl: 'https://cdn.jsdelivr.net/npm/:name@:version/dist/:path'
  31. }
  32. }
  33. // 添加plugin
  34. new WebpackCdnPlugin(cdnLoader(true))

2、合理使用contenthash

css、js的filename使用contenthash,只有内容修改的文件才去更新hash值,
这样旧文件可以使用缓存,新内容才去加载新的资源,减少不必要的请求�

  1. output: {
  2. // 打包后的路径
  3. path: resolve('../dist'),
  4. // 打包后的文件名,默认打包出来是main.js
  5. filename: 'js/[name].[contenthash:6].js',
  6. // publicPath: 'https://cloud-app.com.cn/app/',
  7. },

3、合理使用MiniCssExtractPlugin


通过MiniCssExtractPlugin将css提取成一个文件,减少单个js文件大小

  1. // loader
  2. {
  3. test: /\.css/,
  4. use: [
  5. /**
  6. * MiniCssExtractPlugin提取css为一个文件,MiniCssExtractPlugin没有hdr,
  7. * 所以开发使用style-loader
  8. */
  9. devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
  10. // 'style-loader', // 将css文件打包到js
  11. 'css-loader', // css文件处理
  12. ]
  13. },
  14. // plugin
  15. // 提取css文件
  16. new MiniCssExtractPlugin({
  17. filename: 'css/[name].[contenthash:6].css',
  18. }),

缺点:MiniCssExtractPlugin提取css为一个文件,但是没有hdr【热更新】,所以开发使用style-loader

4、代码分割

app.js 主业务代码
common.js 公共依赖
vendors.js 第三方包
manifest.js webpack配置

单页面应用

效果:主业务代码+异步模块 +第三方包+webpack运行代码

  • 减少文件体积,拆分应用

把需要异步加载的内容改成异步加载

  1. require.ensure(['./async1'], () => {
  2. }, 'async1');
  • 拆分第三方依赖和webpack配置
    1. splitChunks: {
    2. name: true,
    3. chunks: 'all',
    4. minSize: 10000, // 大于10kb,再去提取
    5. // 指定需要打包哪些内容
    6. cacheGroups: {
    7. vendor: {
    8. // 第三方包
    9. test: /[\\/]node_modules[\\/]/,
    10. chunks: 'initial',
    11. enforce: true,
    12. priority: 10,
    13. name: 'vendor'
    14. },
    15. // common: {
    16. // // 公共资源的打包
    17. // chunks: "all",
    18. // minChunks: 2,
    19. // name: 'common',
    20. // enforce: true,
    21. // priority: 5
    22. // }
    23. },
    24. },
    25. // 运行时,webpack配置文件
    26. // runtimeChunk: true,
    27. runtimeChunk: {
    28. "name": "manifest"
    29. },
    打包效果
    image.png

多页面应用

效果:主业务代码+公共依赖+第三方+webpack运行代码

  • 提取公共依赖
  • 拆分第三方依赖和webpack配置
    1. optimization: {
    2. splitChunks: {
    3. name: true,
    4. chunks: 'all',
    5. minSize: 10000, // 大于10kb,再去提取
    6. // 指定需要打包哪些内容
    7. cacheGroups: {
    8. vendor: {
    9. // 第三方包
    10. test: /[\\/]node_modules[\\/]/,
    11. chunks: 'initial',
    12. enforce: true,
    13. priority: 10,
    14. name: 'vendor'
    15. },
    16. common: {
    17. // 公共资源的打包
    18. chunks: "all",
    19. minChunks: 2,
    20. name: 'common',
    21. enforce: true,
    22. priority: 5
    23. }
    24. },
    25. },
    26. // 运行时,webpack配置文件
    27. // runtimeChunk: true,
    28. runtimeChunk: {
    29. "name": "manifest"
    30. },
    31. }

打包结果
image.png

5、代码体积控制

生产模式默认开启

  1. optimization: {
  2. // 开启代码压缩,mode为production默认开启代码压缩和TreeShaking
  3. // minimize: true,
  4. }

webpack3当中使用optimize.UglifyJsPlugin
image.png

6、Tree-shaking

生产模式默认开启
webpack3当中使用optimize.UglifyJsPlugin

作用

Tree-shaking指的是消除没被引用的模块代码,减少代码体积大小,以提高页面的性能,最初由rollup提出

webpack2加入对Tree-shaking的支持,webpack4Tree-shaking默认开启,Tree-shaking基于ESModule静态编译而成,可能会被babel所干扰【export会被编译】

注意事项:

  • 不使用CommonJs模块
  • 不让babel编译成CommonJs的形式

添加modulesfalse,保留es6的模块化语法不去编译

  1. {
  2. "presets": [
  3. ["@babel/preset-env", {
  4. "modules": false, // 保留es6的模块化语法
  5. "targets": {
  6. "browsers": ["> 1%", "last 2 versions", "not ie <= 8", "iOS 8"]
  7. }
  8. }]
  9. ]
  10. }

7、打包速度优化

项目本身

a、减少嵌套深度
b、使用尽可能少的处理

打包结果分析

webpack-bundle-analyzer

Dll打包优化

动态链接库

新建webpack.dll.js
entry为需要提前处理的第三方库
output不能为dist,可以放在static下面,library为包的引用名称
当然,最好也是每次都CleanWebpackPlugin清除一下
使用webpack.DllPlugin,生成处理后的包及JSON

  1. /**
  2. * @Author: forguo
  3. * @Date: 2022/4/3 10:45
  4. * @Description: webpack.dll.js
  5. */
  6. const path = require('path');
  7. const webpack = require('webpack');
  8. // 在每次 build 后移除你的dist目录(可配置),默认情况下它会读取 webpack 配置的output.path。
  9. const { CleanWebpackPlugin } = require('clean-webpack-plugin');
  10. // 拼接路径
  11. const resolve = dir => path.join(__dirname, dir);
  12. module.exports = {
  13. mode: 'none',
  14. entry: {
  15. lodash: ['lodash'],
  16. },
  17. output: {
  18. // 打包后的路径
  19. path: resolve('../static/dll'),
  20. filename: '[name].js',
  21. library: '[name]', // 引用名
  22. },
  23. plugins: [
  24. // 清除上次打包的代码
  25. new CleanWebpackPlugin(),
  26. new webpack.DllPlugin({
  27. path: resolve('../static/dll/[name].json'),
  28. name: '[name]',
  29. })
  30. ]
  31. }

image.png
webpack.config.js添加webpack.DllReferencePlugin

  1. new webpack.DllReferencePlugin({
  2. manifest: require('../static/dll/lodash.json'), // manifest的位置
  3. }),

缺点:抽离出来的chunk,需要手动引入到html当中…

happypack

启动多线程编译,webpack4可以使用 thread-loader

  1. {
  2. test: /\.js|jsx$/,
  3. use: ["thread-loader", "babel-loader?cacheDirectory=true"],
  4. include: path.resolve(__dirname, '../src')
  5. },

Uglify优化

webpack3当中,添加cacheparalleltrue

  1. new UglifyJsPlugin({
  2. uglifyOptions: {
  3. compress: {
  4. warnings: false
  5. }
  6. },
  7. sourceMap: config.build.productionSourceMap,
  8. parallel: true, // 并行处理压缩
  9. cache: true, // 处理缓存
  10. }),

长缓存优化

只取变化修改的内容文件名,利用浏览器缓存,更新更少的资源
https://www.cnblogs.com/skychx/p/webpack-hash-chunkhash-contenthash.html

  • hash

与项目构建有关
每次hash都改变,生成文件的 hash 和项目的构建 hash一致

  • chunkhash

与同一chunk内容有关
根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。

  • contenthash

与文件内容本身有关
contenthash 将根据资源内容创建出唯一 hash,也就是说文件内容不变,hash 就不变。

自定义loader和plugin

自定义loader

其实就是对于字符串的处理

  1. /**
  2. * @Author: forguo
  3. * @Date: 2021/12/11 15:13
  4. * @Description: 一个自定义的loader
  5. */
  6. module.exports = function (resource) {
  7. /**
  8. * 将$xx转换成 wei-xxx
  9. * @type {RegExp}
  10. */
  11. const reg = /\$\(/g;
  12. try {
  13. return resource.replace(reg, 'wei-').replace(')', '');
  14. } catch (e) {
  15. console.log('ws-loader-error', e);
  16. }
  17. }

自定义plugin

对于打包之后结果的处理

  1. /**
  2. * @Author: forguo
  3. * @Date: 2022/4/5 18:54
  4. * @Description: 自定义plugin html-webpack-add-static-server
  5. */
  6. const path = require('path');
  7. const fs = require('fs');
  8. const readFileAsync = require("util").promisify(fs.readFile);
  9. const writeFileAsync = require("util").promisify(fs.writeFile);
  10. class AddStaticServer {
  11. constructor(options) {
  12. this.options = options || {
  13. serverPath: '//www'
  14. };
  15. this.serverPath = this.options.serverPath;
  16. }
  17. apply(compiler) {
  18. compiler.hooks.done.tap('AddStaticServer', compilation => {
  19. let context = compiler.options.context;
  20. let publicPath = path.resolve(context, 'dist');
  21. compilation.toJson().assets.forEach((ast) => {
  22. let {dir, base, ext} = path.parse(ast.name);
  23. if (ext === '.ftl') {
  24. readFileAsync(path.resolve(publicPath, dir, base), {encoding: 'utf-8'}).then((cnt) => {
  25. cnt = cnt.replace(/\/static\/css/g, `${this.serverPath}res/css`)
  26. .replace(/\/static\/js/g, `${this.serverPath}res/js`)
  27. .replace(/\/static\/img/g, `${this.serverPath}res/img`);
  28. writeFileAsync(path.resolve(publicPath, dir, base), cnt);
  29. });
  30. }
  31. });
  32. });
  33. }
  34. }
  35. module.exports = AddStaticServer;