Webpack4 高级概念 常用配置

本文记录webpack的一些常用高级概念,需要对webapck基础有所了解,本文重点记录概念涉及到的配置,其他配置有所省略,更多内容参照官方文档。
webpack中文网
webpack英文网

Tree Shaking

作用是当引入一个模块时,不会引入模块全部代码,只引入需要的部分代码,Tree Shaking只支持ES Module,不支持CommonJS模块方式。

例如只引入match.js的add方法

// match.js
export function add(a, b) {
  return a + b
}
export function minus(a, b) {
  return a - b
}

// index.js
import { add } from './math'

webpack配置

// webpack.config.js 生产环境
module.exports = {
  mode: 'production',
  devtool: 'cheap-module-source-map',
}

// webpack.config.js 开发环境
module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  optimization: {
    usedExports: true
  }
}

// package.json 如果某些文件不需要做Tree Shaking,需要增加如下配置
"sideEffects": false, // 没有要处理的文件
"sideEffects": ["*.css"], // 任何css文件都不做Tree Shaking

Develoment 和 Production 模式的区分打包

webpack.common.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
  entry: {
    main: './src/index.js'
  },
  module: {
    // 相关loader配置(此处省略)
    rules: [ ... ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }), 
    new CleanWebpackPlugin(['dist'], {
      root: path.resolve(__dirname, '../')
    })
  ],
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, '../dist')
  }
}

webpack.dev.js

const webpack = require('webpack')
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')

const devConfig = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    contentBase: './dist',
    open: true,
    port: 8080,
    hot: true
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ],
  optimization: {
    usedExports: true
  }
}

module.exports = merge(commonConfig, devConfig)

webpack.prod.js

const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')

const prodConfig = {
  mode: 'production',
  devtool: 'cheap-module-source-map'
}

module.exports = merge(commonConfig, prodConfig)

Webpack 和 Code Splitting

  • 代码分割,和webpack无关
  • webpack中实现代码分割,两种方式
    • 同步代码: 只需要在webpack.common.js中做optimization的配置即可
    • 异步代码(import): 异步代码,无需做任何配置,会自动进行代码分割,放置到新的文件中

同步加载

// index.js
import _ from 'lodash'
console.log(_.join(['a', 'b'], '-'))

// webpack.common.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
}

异步加载

注意需要借助babel插件来支持该语法

npm install --save-dev @babel/plugin-syntax-dynamic-import
// .babelrc
{
  plugins: ["@babel/plugin-syntax-dynamic-import"]
}
// index.js
function getComponent() {
  return import(/* webpackChunkName:"lodash" */ 'lodash').then(({ default: _ }) => {
    var element = document.createElement('div')
    element.innerHTML = _.join(['a', 'b'], '-')
    return element
  })
}

getComponent().then(element => {
  document.body.appendChild(element)
})

/* webpackChunkName:"lodash" */webpack魔法注释的作用是对异步加载的lodash打包生成的文件进行重命名,即为vendors~lodash.js,不设置的时候是0.js

SplitChunksPlugin详细配置参数

详细配置项详见webpack官方文档

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async', // 对异步模块进行优化,结合cacheGroups生效,有效值是all、async(异步)和initial(同步)
      minSize: 30000, // 引入的模块大于30kb才做代码分割
      maxSize: 50000, // 如果分割的lodash大于50kb,则尝试进行二次分割(了解即可)
      minChunks: 2, // 当一个模块至少用了2次的时候才进行代码分割
      maxAsyncRequests: 5, // 同时只能加载五个请求,超过五个就不会做代码分割
      maxInitialRequests: 3, // 入口文件引入的库如果做代码分割最多只能分割出3个文件
      automaticNameDelimiter: '~', // 默认打包生成文件名的连接符
      name: true, // cacheGroups中改变的名字生效
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/, // 是node_modules中引入的模块
          priority: -10, // 优先级值越大优先级越高
          filename: 'vendors.js' // 打包生成的文件名
        },
        default: {
          priority: -20, // 优先级值越大优先级越高
          reuseExistingChunk: true, // 如果模块已经被打包过,则忽略此模块,直接复用
          filename: 'commin.js' // 打包生成的文件名
        }
      }
    }
  }
}

Lazy Loading 懒加载

懒加载其实就是上文 Code Splitting 中提到的异步加载模块,指的是需要的时候再去加载该模块

// index.js
async function getComponent() {
  const { default: _ } = await import(/* webpackChunkName:"lodash" */ 'lodash')
  const element = document.createElement('div')
  element.innerHTML = _.join(['a', 'b'], '-')
  return element
}

document.addEventListener('click', () =>{
  getComponent().then(element => {
    document.body.appendChild(element)
  })
})

打包分析

暂不做详细介绍(后续补充),详细查看官方文档。

webpack-analyse
官方文档

build命令需要增加 --profile 参数

// package.js
{
  "scripts": {
    "dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js"
  }
}

Preloading,Prefetching

  • preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
  • preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。

官方文档

// click.js
function handleClick() {
  const element = document.createElement('div')
  element.innerHTML = 'hello world'
  document.body.appendChild(element)
}

export default handleClick
// index.js
document.addEventListener('click', () =>{
  import(/* webpackPrefetch: true */ './click.js').then(({default: func}) => {
    func();
  })
})

/* webpackPrefetch: true */等有空闲带宽时加载此模块

CSS 文件的代码分割 MiniCssExtractPlugin

官方文档

# 代码分割插件
npm install --save-dev mini-css-extract-plugin
# 代码压缩合并插件
npm install --save-dev optimize-css-assets-webpack-plugin

MiniCssExtractPlugin插件目前还不支持HMR support热更新,一般在线上环境做打包时使用

webpack.common.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
  entry: {
    main: './src/index.js',
  },
  module: {
    rules: [{ 
      test: /\.js$/, 
      exclude: /node_modules/, 
      loader: 'babel-loader',
    }, {
      test: /\.(jpg|png|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name]_[hash].[ext]',
          outputPath: 'images/',
          limit: 10240
        }
      } 
    }, {
      test: /\.(eot|ttf|svg)$/,
      use: {
        loader: 'file-loader'
      } 
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }), 
    new CleanWebpackPlugin(['dist'], {
      root: path.resolve(__dirname, '../')
    })
  ],
  optimization: {
    usedExports: true, // 注意这里要对css文件不做Tree Shaking,具体在package.json中配置
    splitChunks: {
      chunks: 'all'
    }
  },
  output: {
    filename: '[name].js', // entry直接引入的(入口)文件走这里
    chunkFilename: '[name].chunk.js', // 间接引入的文件走这里
    path: path.resolve(__dirname, '../dist')
  }
}

webpack.dev.js

const webpack = require('webpack');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');

const devConfig = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    contentBase: './dist',
    open: true,
    port: 8080,
    hot: true
  },
  module: {
    rules: [{
      test: /\.scss$/,
      use: [
        'style-loader', 
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: [
        'style-loader',
        'css-loader',
        'postcss-loader'
      ]
    }]
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ],
}

module.exports = merge(commonConfig, devConfig);

webpack.prod.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');

const prodConfig = {
  mode: 'production',
  devtool: 'cheap-module-source-map',
  module: {
    rules:[{
      test: /\.scss$/,
      use: [
        MiniCssExtractPlugin.loader, 
        {
          loader: 'css-loader',
          options: {
            importLoaders: 2
          }
        },
        'sass-loader',
        'postcss-loader'
      ]
    }, {
      test: /\.css$/,
      use: [
        MiniCssExtractPlugin.loader,
        'css-loader',
        'postcss-loader'
      ]
    }]
  },
  optimization: {
    // 代码压缩合并插件的配置
    minimizer: [new OptimizeCSSAssetsPlugin({})]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css', // 直接引入的文件走这里
      chunkFilename: '[name].chunk.css' // 间接引入的文件走这里
    })
  ]
}

module.exports = merge(commonConfig, prodConfig);

package.json

// css文件不做Tree Shaking
{
  "sideEffects": [
    "*.css"
  ]
}

Webpack 与浏览器缓存

// webpack.common.js
module.exports = {
  optimization: {
    // 老版本webpack4需要runtimeChunk配置(处理没改变业务代码也会改变打包后的contenthash情况)
    runtimeChunk: {
      name: 'runtime'
    },
    usedExports: true,
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          name: 'vendors',
        }
      }
    }
  },
  performance: false, // 性能问题不做警告
  output: {
    path: path.resolve(__dirname, '../dist')
  }
}

// webpack.prod.js
module.exports = {
  output: {
    // 文件名添加contenthash值
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js'
  }
}

Shimming 的作用

index.js

import $ from 'jquery'
import _ from 'lodash'
import { ui } from './jquery.ui'

ui()

const dom = $('<div>')
dom.html(_.join(['a', 'b'], '-'))
$('body').append(dom)

jquery.ui.js

// 在另一个模块中直接使用jquery和lodash方法
export function ui() {
  $('body').css('background', _join(['red'], ''))
}

webpack.common.js

const webpack = require('webpack')

module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery', // 在一个模块引入可以在其他模块直接用$
      _join: ['lodash', 'join'] // _.join的另一种方式,可以直接用_join
    })
  ]
}

环境变量的使用方法

在上文 Develoment 和 Production 模式的区分打包 中的区分打包可以改为使用环境变量的方式。即webpack.common.js中通过变量判断是合并开发环境还是生产环境的配置,webpack.dev.jswebpack.prod.js只写各自配置,不需要单独合并引入。

webpack.common.js

const merge = require('webpack-merge')
const devConfig = require('./webpack.dev.js')
const prodConfig = require('./webpack.prod.js')
const commonConfig = {
  ...
}

module.exports = (env) => {
  if(env && env.production) {
    return merge(commonConfig, prodConfig);
  } else {
    return merge(commonConfig, devConfig);
  }
}

webpack.dev.js或webpack.prod.js直接导出,不用合并webpack.common.js

const devConfig = {
  ...
}

module.exports = devConfig;

package.json

注意npm run build中需要传入--env.production变量,其他都是直接引入webpack.common.js文件。

{
  "scripts": {
    "dev-build": "webpack --config ./build/webpack.common.js",
    "dev": "webpack-dev-server --config ./build/webpack.common.js",
    "build": "webpack --env.production --config ./build/webpack.common.js"
  }
}
posted @ 2021-05-08 13:51  lwlcode  阅读(200)  评论(0编辑  收藏  举报