面试题分享
可视化搭建webpack环境
image.png

从0到1 - 图2

初始指令

  1. 开发环境:webpack ./src/index.js -o ./build/index.js --mode=development
  2. 生产环境:webpack ./src/index.js -o ./build/index.js --mode=production
  3. # 注意点,这里设置的mode变量,在webpack配置文件中需要通过argv这个参数获取,
  4. # 而不是直接通过process.env.NODE_ENV获取
  5. module.exports = (env, argv) => {
  6. const env = argv.mode
  7. }

配置文件

webpack.config.js

loader

  • less-loader
    • 将less文件编译成css文件
    • 需要下载 less-loader、less俩个npm包
  • css-loader
    • 将css文件变成commonjs模块加载到js文件中,里面的内容是样式字符串
  • style-loader
    • 创建style标签,将js中的样式资源插入到head标签中
  • url-loader
    • 图片文件处理
  • file-loader
    • 处理文件

一般url-loaderfile-loader一起配合使用

  • postcss-loader
    • webpack和postcss预处理器结合
  • eslint-loader
    • 语法检查
  • babel-loader

    • js语法兼容
    • 需要配合 @babel/core 使用
      • 基本js兼容处理: @babel/preset-env 【@babel/preset-env 只能对基本语法进行转换】 例如promise等语法不能兼容
      • 全部js兼容性处理:@babel/polyfill 不需要在webpack中配置,而是在index.js[入口文件] 中import使用
      • 按需兼容: core-js 使用这一种方案之后,不能使用第二种方案
    • 一般使用第一种和第三种一起完成js兼容性处理

      plugins

  • html-webpack-plugin

    • 默认会创建一个空的Html文件,自动引入打包输出的所有资源文件
  • mini-css-extract-plugin
    • css单独打包成一个文件
    • 该插件使用不能和style-loader一起使用
  • optimize-css-assets-webpack-plugin
    • 压缩css文件 ```javascript const { resolve } = require(‘path’); const HtmlWebpackPlugin = require(‘html-webpack-plugin’) const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’) const OptimizeCssAssetsWebpackPlugin = require(‘optimize-css-assets-webpack-plugin’)

module.exports = { entry: ‘./src/index.js’, output: { filename: ‘build.js’, path: resolve(dirname, ‘build’), environment: { arrowFunction: false, } }, module: { rules: [ { test: /.css$/, use: [ // ‘style-loader’, // 样式存放在style标签中 MiniCssExtractPlugin.loader, // 样式存放在单独css文件中 ‘css-loader’, // css-loader { // postcss-loader loader: ‘postcss-loader’, options: { postcssOptions: { ident: ‘postcss’, plugins: () => [ require(‘postcss-preset-env’)() ] } } } ] }, { test: /.less/, use: [ // ‘style-loader’, // 样式存放在style标签中 MiniCssExtractPlugin.loader, // 样式存放在单独css文件中 ‘css-loader’, // css-loader { // postcss-loader loader: ‘postcss-loader’, options: { postcssOptions: { ident: ‘postcss’, plugins: () => [ require(‘postcss-preset-env’)() ] } } }, ‘less-loader’ // less-loader ] }, // 下面的loader只能处理js和css文件中使用的图片资源,但是在html中使用图片资源时,不能够被正确的处理 { test: /.(jpg|png|gif)$/, loader: ‘url-loader’, options: { limit: 10 * 1024, // 当图片资源小于10kb时,我们使用base64来替代图片 // url-loader 默认使用esmodule来进行解析, // html-loader默认使用的是commonjs来解析, 所以我们关闭url-loader的es6模块化 esModule: false, name: ‘[hash:10].[ext]’, output: ‘images’ } }, { test: /.html$/, loader: ‘html-loader’,// 解决上面图片资源直接在html文件中引入问题 }, { exclude: /.(css|js|html|less|jpg|png|gif)$/, loader: ‘file-loader’, options: { output: ‘media’ name: ‘[hash:10].[ext]’ } }, { test: /.js$/, exclude: /node_modules/, loader: ‘eslint-loader’, // 需要在package.json中配置eslint的风格 enforce: ‘pre’, // 当loader中有俩个相同的文件匹配时,通过这个配置指定loader执行先后顺序 options: { // 自动修复 fix: true } }, { test: /.js$/, exclude: /node_modules/, loader: ‘babel-loader’, options: { presets: [[ ‘@babel/preset-env’, { useBuiltIns: ‘usage’, // 指定core-js版本 corejs: { version: 3 }, // 指定兼容性支持到哪个版本浏览器 targets: { chrome: ‘60’, firefox: ‘60’, ie: ‘9’, safari: ‘10’, edge: ‘17’ } } ]] } } ] }, plugins: [ new HtmlWebpackPlugin({ template: ‘./src/index.html’ // 复制模版文件,并自动引入静态资源 minify: { collapseWhitespace: true, // 移除空格 removeComments: true, // 移除注释 } }), new MiniCssExtractPlugin({ filename: ‘css/index.css’ }), new OptimizeCssAssetsWebpackPlugin() ], mode: ‘development’, // production devServer: { contentBase: resolve(dirname, ‘build’), compress true, port: 3000, hot: true, // 开启HMR open: true // 自动打开浏览器 } }

<a name="rFzxP"></a>
#### devServer
自动编译,在开发过程中每次修改代码之后不需要重新打包
```javascript
启动指令
npx webpack-dev-server
需要通过npm下载webpack-dev-server

css兼容性处理

postcss -> postcss-loader -> post-preset-env
post-preset-env 帮postcss找到package.json中browserlist里面的配置,通过配置加载指定的css兼容性样式
browserslist配置参考链接

// package.json
{
  "browserslist": { // 下面俩个模式根据process.env.NODE_ENV来区分的
     "development": [
       "last 1 chrome version",
       "last 1 firefox version",
       "last 1 safari version"
     ],
     "production": [
       ">0.2%",
       "not dead",
       "not op_mini all"
     ]
  }
}

语法检查

eslint-loader
一般使用airbnb的代码风格
yarn add eslint-config-airbnb-base eslint-plugin-import eslint

// package.json
{
  "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
      "browser": true
    }
  }
}

压缩

js压缩:mode设置为production即可
html压缩:设置HtmlWebpackPlugin的minify配置

性能优化

从0到1 - 图3

HMR

模块热更新:一个模块发生变化,只会重新打包这一个模块

  • css文件使用:开发环境下使用style-loader来代替MiniCssExtractPlugin.loader,style-loader为我们实现了HMR功能
  • js文件使用:默认不能使用

    • 需要修改js代码
      if(module.hot) {
      module.hot.accept('./xxx.js', function() {
      // 执行该模块中的方法,重新调用
      })
      }
      
  • html文件使用:默认不能使用

    • 解决:修改entry入口[‘./src/index.js’,’./src/index.html’]
    • 但是一般html文件不需要做HMR ```json webpack.dev.config.js

const { resolve } = require(‘path’); const HtmlWebpackPlugin = require(‘html-webpack-plugin’) const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’) const OptimizeCssAssetsWebpackPlugin = require(‘optimize-css-assets-webpack-plugin’)

module.exports = { entry: [‘./src/index.js’,’./src/index.html’], output: { filename: ‘build.js’, path: resolve(dirname, ‘build’) }, module: { rules: [ { test: /.css$/, use: [ ‘style-loader’, // 样式存放在style标签中 // MiniCssExtractPlugin.loader, // 样式存放在单独css文件中 ‘css-loader’, // css-loader { // postcss-loader loader: ‘postcss-loader’, options: { postcssOptions: { ident: ‘postcss’, plugins: () => [ require(‘postcss-preset-env’)() ] } } } ] }, { test: /.less/, use: [ ‘style-loader’, // 样式存放在style标签中 // MiniCssExtractPlugin.loader, // 样式存放在单独css文件中 ‘css-loader’, // css-loader { // postcss-loader loader: ‘postcss-loader’, options: { postcssOptions: { ident: ‘postcss’, plugins: () => [ require(‘postcss-preset-env’)() ] } } }, ‘less-loader’ // less-loader ] }, // 下面的loader只能处理js和css文件中使用的图片资源,但是在html中使用图片资源时,不能够被正确的处理 { test: /.(jpg|png|gif)$/, loader: ‘url-loader’, options: { limit: 10 * 1024, // 当图片资源小于10kb时,我们使用base64来替代图片 // url-loader 默认使用esmodule来进行解析, // html-loader默认使用的是commonjs来解析, 所以我们关闭url-loader的es6模块化 esModule: false, name: ‘[hash:10].[ext]’, output: ‘images’ } }, { test: /.html$/, loader: ‘html-loader’,// 解决上面图片资源直接在html文件中引入问题 }, { exclude: /.(css|js|html|less|jpg|png|gif)$/, loader: ‘file-loader’, options: { output: ‘media’ name: ‘[hash:10].[ext]’ } }, { test: /.js$/, exclude: /node_modules/, loader: ‘eslint-loader’, // 需要在package.json中配置eslint的风格 enforce: ‘pre’, // 当loader中有俩个相同的文件匹配时,通过这个配置指定loader执行先后顺序 options: { // 自动修复 fix: true } }, { test: /.js$/, exclude: /node_modules/, loader: ‘babel-loader’, options: { presets: [[ ‘@babel/preset-env’, { useBuiltIns: ‘usage’, // 指定core-js版本 corejs: { version: 3 }, // 指定兼容性支持到哪个版本浏览器 targets: { chrome: ‘60’, firefox: ‘60’, ie: ‘9’, safari: ‘10’, edge: ‘17’ } } ]] } } ] }, plugins: [ new HtmlWebpackPlugin({ template: ‘./src/index.html’ // 复制模版文件,并自动引入静态资源 minify: { collapseWhitespace: true, // 移除空格 removeComments: true, // 移除注释 } }), new MiniCssExtractPlugin({ filename: ‘css/index.css’ }), new OptimizeCssAssetsWebpackPlugin() ], mode: ‘development’, // production devServer: { contentBase: resolve(dirname, ‘build’), compress true, port: 3000, hot: true, // 开启HMR open: true // 自动打开浏览器 } }

<a name="knHFB"></a>
### source-map
开发环境一般使用:eval-source-map<br /> 生产环境一般使用:source-map / cheap-module-source-map 
```json
module.exports = {
  devtool: 'source-map' // 可选项见下方
}
  • source-map
    • 错误代码准确信息 和 源代码的错误信息位置
  • inline-source-map
    • 只生成一个内联source-map
    • 错误代码准确信息 和 源代码的错误信息位置
  • hidden-source-map
    • 提示错误原因,但是没有错误位置,不能追踪源代码错误,只能提示到构建后代码的错误
  • eval-source-map
    • 每一个文件都生成对应的source-map
    • 错误代码准确信息 和 源代码的错误信息位置
  • nosources-source-map
    • 错误代码准确信息 ,但是找不到源代码
  • cheap-source-map
    • 错误代码准确信息 和 源代码的错误信息位置
    • 但是只能精确到错误行

      oneOf

      提升构建速度,其中的loader只会匹配一个
      使用时需要注意,不能在其中配置处理同一种类型文件的loader ```json webpack.dev.config.js

const { resolve } = require(‘path’); const HtmlWebpackPlugin = require(‘html-webpack-plugin’) const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’) const OptimizeCssAssetsWebpackPlugin = require(‘optimize-css-assets-webpack-plugin’)

module.exports = { entry: [‘./src/index.js’,’./src/index.html’], output: { filename: ‘build.js’, path: resolve(dirname, ‘build’) }, module: { rules: [ { test: /.js$/, exclude: /node_modules/, loader: ‘eslint-loader’, // 需要在package.json中配置eslint的风格 enforce: ‘pre’, // 当loader中有俩个相同的文件匹配时,通过这个配置指定loader执行先后顺序 options: { // 自动修复 fix: true } }, { oneOf: [ { test: /.css$/, use: [ ‘style-loader’, // 样式存放在style标签中 // MiniCssExtractPlugin.loader, // 样式存放在单独css文件中 ‘css-loader’, // css-loader { // postcss-loader loader: ‘postcss-loader’, options: { postcssOptions: { ident: ‘postcss’, plugins: () => [ require(‘postcss-preset-env’)() ] } } } ] }, { test: /.less/, use: [ ‘style-loader’, // 样式存放在style标签中 // MiniCssExtractPlugin.loader, // 样式存放在单独css文件中 ‘css-loader’, // css-loader { // postcss-loader loader: ‘postcss-loader’, options: { postcssOptions: { ident: ‘postcss’, plugins: () => [ require(‘postcss-preset-env’)() ] } } }, ‘less-loader’ // less-loader ] }, // 下面的loader只能处理js和css文件中使用的图片资源,但是在html中使用图片资源时,不能够被正确的处理 { test: /.(jpg|png|gif)$/, loader: ‘url-loader’, options: { limit: 10 * 1024, // 当图片资源小于10kb时,我们使用base64来替代图片 // url-loader 默认使用esmodule来进行解析, // html-loader默认使用的是commonjs来解析, 所以我们关闭url-loader的es6模块化 esModule: false, name: ‘[hash:10].[ext]’, output: ‘images’ } }, { test: /.html$/, loader: ‘html-loader’,// 解决上面图片资源直接在html文件中引入问题 }, { exclude: /.(css|js|html|less|jpg|png|gif)$/, loader: ‘file-loader’, options: { output: ‘media’ name: ‘[hash:10].[ext]’ } }, { test: /.js$/, exclude: /node_modules/, loader: ‘babel-loader’, options: { presets: [[ ‘@babel/preset-env’, { useBuiltIns: ‘usage’, // 指定core-js版本 corejs: { version: 3 }, // 指定兼容性支持到哪个版本浏览器 targets: { chrome: ‘60’, firefox: ‘60’, ie: ‘9’, safari: ‘10’, edge: ‘17’ } } ]] } } ] } ] }, plugins: [ new HtmlWebpackPlugin({ template: ‘./src/index.html’ // 复制模版文件,并自动引入静态资源 minify: { collapseWhitespace: true, // 移除空格 removeComments: true, // 移除注释 } }), new MiniCssExtractPlugin({ filename: ‘css/index.css’ }), new OptimizeCssAssetsWebpackPlugin() ], mode: ‘development’, // production devServer: { contentBase: resolve(dirname, ‘build’), compress true, port: 3000, hot: true, // 开启HMR open: true // 自动打开浏览器 } }

<a name="w4WTp"></a>
### 缓存

- 针对babel开启构建缓存
   - cacheDirectory: true
- 通过hash后缀进行静态资源缓存
   - hash 每一次编译都会生成一个新的hash
   - chunkhash 根据打包的chunk生成hash值
      - 可能由于代码里引入了别的模块导致的变化而改变hash值
   - contenthash 根据文件内容变动生成hash值
```json
webpack.dev.config.js

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

module.exports = {
  entry: ['./src/index.js','./src/index.html'],
  output: {
    filename: 'build.[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'eslint-loader', // 需要在package.json中配置eslint的风格
        enforce: 'pre', // 当loader中有俩个相同的文件匹配时,通过这个配置指定loader执行先后顺序
        options: {
          // 自动修复
          fix: true
        }
      },
      {
        oneOf: [
          {
            test: /\.css$/,
            use: [
              'style-loader', // 样式存放在style标签中
              // MiniCssExtractPlugin.loader, // 样式存放在单独css文件中
              'css-loader', // css-loader
              { // postcss-loader
                loader: 'postcss-loader',
               options: {
                  postcssOptions: {
                    ident: 'postcss',
                    plugins: () => [
                      require('postcss-preset-env')()
                    ]
                  }
                }
              }
            ]
          },
          {
            test: /\.less/,
             use: [
              'style-loader', // 样式存放在style标签中
             // MiniCssExtractPlugin.loader, // 样式存放在单独css文件中
              'css-loader',  // css-loader
              { // postcss-loader
                loader: 'postcss-loader',
                options: {
                  postcssOptions: {
                    ident: 'postcss',
                    plugins: () => [
                      require('postcss-preset-env')()
                    ]
                  }
                }
              },
             'less-loader' // less-loader
            ]
          },
          // 下面的loader只能处理js和css文件中使用的图片资源,但是在html中使用图片资源时,不能够被正确的处理
          {
            test: /\.(jpg|png|gif)$/,
            loader: 'url-loader',
            options: {
              limit: 10 * 1024, // 当图片资源小于10kb时,我们使用base64来替代图片
              // url-loader 默认使用esmodule来进行解析,
              // html-loader默认使用的是commonjs来解析, 所以我们关闭url-loader的es6模块化
              esModule: false, 
              name: '[hash:10].[ext]',
              output: 'images'
            }
          },
          {
            test: /\.html$/,
            loader: 'html-loader',// 解决上面图片资源直接在html文件中引入问题
          },
          {
            exclude: /\.(css|js|html|less|jpg|png|gif)$/,
            loader: 'file-loader',
            options: {
              output: 'media'
              name: '[hash:10].[ext]'
            }
          },
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
              presets: [[
                '@babel/preset-env',
                {
                  useBuiltIns: 'usage',
                  // 指定core-js版本
                  corejs: {
                    version: 3
                  },
                  // 指定兼容性支持到哪个版本浏览器
                  targets: {
                    chrome: '60',
                    firefox: '60',
                    ie: '9',
                    safari: '10',
                    edge: '17'
                  }
                }
              ]],
              cacheDirectory: true
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
        template: './src/index.html' // 复制模版文件,并自动引入静态资源
        minify: {
          collapseWhitespace: true, // 移除空格
          removeComments: true, // 移除注释
        }
      }),
    new MiniCssExtractPlugin({
      filename: 'css/index.[contenthash:10].css'
    }),
    new OptimizeCssAssetsWebpackPlugin()
    ],
}

tree shaking

  • 去除无用的js代码,减少代码体积
    • 使用production模式
    • 使用ES6模块化
  • 可能出现的问题:会将css/less等直接导入的文件丢失
    • 可以通过在package.json中配置”sideEffects”: [“.css”, “.less”, “./src/polyfill.js”]
    • 或者可以通过 import styles from ‘./xxx.less’ 这种css-module的方式去使用
    • 副作用配置链接

code split

代码分割

  • 可以将node_modules中代码单独打包成一个chunk输出
  • 自动分析多入口文件,会将公共文件单独打包成一个chunk

当我们在js代码中使用import(‘xxxx.js’) 这种语法的时候,会默认将我们引入的js模块单独打包成一个chunk,在react或者vue中我们router中import组件和这种方式一致

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
}

多进程打包

{
            test: /\.js$/,
            exclude: /node_modules/,
            use: [
              {
                loader: 'thread-loader',
                options: {
                  workers: 2
                }
              },
              {
                loader: 'babel-loader',
                options: {
                  presets: [[
                    '@babel/preset-env',
                    {
                      useBuiltIns: 'usage',
                      // 指定core-js版本
                      corejs: {
                        version: 3
                      },
                      // 指定兼容性支持到哪个版本浏览器
                      targets: {
                        chrome: '60',
                        firefox: '60',
                        ie: '9',
                        safari: '10',
                        edge: '17'
                      }
                    }
                  ]],
                  cacheDirectory: true
                }
              }
            ],
          }

externals

忽略第三方库被打包,需要通过cdn的方式去引入依赖包

externals: {
  'jquery': 'jQuery
}

dll

打包一次第三方的包或者库,后面不再打包

最后配置

webpack.dev.config.js

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

module.exports = {
  entry: ['./src/index.js'],
  output: {
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'eslint-loader', // 需要在package.json中配置eslint的风格
        enforce: 'pre', // 当loader中有俩个相同的文件匹配时,通过这个配置指定loader执行先后顺序
        options: {
          // 自动修复
          fix: true
        }
      },
      {
        oneOf: [
          {
            test: /\.css$/,
            use: [
              'style-loader', // 样式存放在style标签中
              // MiniCssExtractPlugin.loader, // 样式存放在单独css文件中
              'css-loader', // css-loader
              { // postcss-loader
                loader: 'postcss-loader',
                options: {
                  postcssOptions: {
                    ident: 'postcss',
                    plugins: () => [
                      require('postcss-preset-env')()
                    ]
                  }
                }
              }
            ]
          },
          {
            test: /\.less/,
             use: [
              'style-loader', // 样式存放在style标签中
             // MiniCssExtractPlugin.loader, // 样式存放在单独css文件中
              'css-loader',  // css-loader
              { // postcss-loader
                loader: 'postcss-loader',
                options: {
                  postcssOptions: {
                    ident: 'postcss',
                    plugins: () => [
                      require('postcss-preset-env')()
                    ]
                  }
                }
              },
             'less-loader' // less-loader
            ]
          },
          // 下面的loader只能处理js和css文件中使用的图片资源,但是在html中使用图片资源时,不能够被正确的处理
          {
            test: /\.(jpg|png|gif)$/,
            loader: 'url-loader',
            options: {
              limit: 10 * 1024, // 当图片资源小于10kb时,我们使用base64来替代图片
              // url-loader 默认使用esmodule来进行解析,
              // html-loader默认使用的是commonjs来解析, 所以我们关闭url-loader的es6模块化
              esModule: false, 
              name: '[hash:10].[ext]',
              output: 'images'
            }
          },
          {
            test: /\.html$/,
            loader: 'html-loader',// 解决上面图片资源直接在html文件中引入问题
          },
          {
            exclude: /\.(css|js|html|less|jpg|png|gif)$/,
            loader: 'file-loader',
            options: {
              output: 'media'
              name: '[hash:10].[ext]'
            }
          },
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
              presets: [[
                '@babel/preset-env',
                {
                  useBuiltIns: 'usage',
                  // 指定core-js版本
                  corejs: {
                    version: 3
                  },
                  // 指定兼容性支持到哪个版本浏览器
                  targets: {
                    chrome: '60',
                    firefox: '60',
                    ie: '9',
                    safari: '10',
                    edge: '17'
                  }
                }
              ]],
              cacheDirectory: true
            }
          }
        ]
      }
    ]
  },
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  plugins: [
    new HtmlWebpackPlugin({
        template: './src/index.html' // 复制模版文件,并自动引入静态资源
        minify: {
          collapseWhitespace: true, // 移除空格
          removeComments: true, // 移除注释
        }
      }),
    new MiniCssExtractPlugin({
      filename: 'css/index.[contenthash:10].css'
    }),
    new OptimizeCssAssetsWebpackPlugin()
    ],
  mode: 'development', // production
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress true,
    port: 3000,
    hot: true, // 开启HMR
    open: true // 自动打开浏览器
  }
}