20191002121033.png

entry(入口)

单入口:entry是一个字符串

 module.exports={
    entry:'./path/file.js'
 }  

多入口:entry是一个对象

module.exports={
    entry:{
        app:'./src/app.js',
        adminApp:'./src/adminApp.js'
    }
}

output(输出)

单入口配置

module.exports={
    output:{
        filename:'bundle.js',
        path:_dirname+'/dist'
    }
}

多入口配置

通过占位符确保文件名称的唯一

module.exports={
    output:{
        filename:'[name].js',
        path:_dirname+'/dist'
    }
}

loaders

webpack开箱即用只支持JS和JSON两种文件类型,通过Loaders去支持其他文件类型并且把它们转化成有效的模块,并且可以添加到依赖图中。
本身是一个函数,接受源文件作为参数,返回转换的结果。 test对应匹配规则,use匹配loader

module.exports={
    module:{
        rules:[
            {test:/\.txt$/,use:'raw-loader'}
        ]
    }
}

Plugins

插件用于bundle文件的优化,资源管理和环境变量注入。作用于整个构建过程

module.exports={
    plugins:[new HtmlWebpackPlugin({template:'./src/index.html'})]
}

module

指定当前的构建环境是:production、development还是none(什么都不做), 设置mode可以使用webpack内置的函数,默认值为production 20190911173248.png

热更新:webpack-dev-server

WDS不刷新浏览器
WDS不输出文件,而是放在内存中
使用的是HotModuleReplacementPlugin插件,设置hot:true之后会自动引用,不需要在plugin中添加

文件指纹

文件指纹就是打包后输出的文件名的后缀
Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的hash值就会修改
Chunkhash:和webpack打包的chunk有关,不同的entry会生成不同的chunkhash值
contenthash:根据文件内容来定义hash,文件内容不变,则contenthash不变
推荐使用contenthash

js、css、html压缩

js压缩:webpack4内置uglifyjs-webpack-plugin
css压缩:mini-css-extract-plugin + cssnano html压缩:html-webpack-plugin

资源内联

使用场景:

  1. 页面初始化需要做的的初始化脚本
  2. css内联避免页面闪动
  3. 减少网络请求(小图片或者字体内联)

html和js内联使用raw-loader
css内联使用:

  1. style-loader
  2. html-inline-css-webpack-plugin

多页面打包通用方案

动态获取entry和设置html-webpack-plugin数量
利用glob.sync

提取页面公共资源

  1. 基础库分离(vue、react)
    将基础包通过cdn引入,不打入bundle中,使用html-webpack-externals-plugin
  2. 公共脚本分离(utils中的工具) 利用webpack内置的SplitChunksPlugin进行公共脚本分离

tree shaking(摇树优化)

  1. tree shaking只能作用于ES6模块,Babel的预置默认把任何模块转译成CommonJS模块,你可以简单设置modules: false来解决此问题。
  2. production mode的情况下默认开启
  3. 在具有副作用的代码中tree-shaking会失效,可以使用webpack-deep-scope-plugin插件来优化,但是如果使用了babel+代码具有副作用的情况下,这个插件还是会失效

scope hoisting(打包后代码的优化)

production模式默认开启

打包后有大量函数闭包包裹代码,导致体积增大,运行代码时创建的函数作用域变多,内存开销变大。 import会被转换成__webopack_require
通过scope hoisting插件可以解决这个问题,webpack mode为production时默认开启,必须是ES6语法

plugins:[
    new webpack.optimize.ModuleConcatenationPlugin()
]

动态分割和动态import

适用场景:

  1. 抽离相同代码到一个共享块
  2. 脚本懒加载,使用初始下载的代码更小(首屏优化) 懒加载js脚本的方式:
  3. conmmonJS:require.ensure
  4. ES6:动态import(目前还没有原生支持,需要babel转换)
    如何使用动态import?
  5. 安装babel插件
npm i @babel/plugin-syntax-dynamic-import --save-dev
  1. 配置.babelrc文件
    "plugin":["@babel/plugin-syntax-dynamic-import"]

打包组件库和基础库

  1. 在生成开发版文件和min版文件时,配置mode:'none'来避免打包后文件都为压缩状态的情况
const TerserPlugin=require('terser-webpack-plugin')
module.exports={
    mode:"none",
    entry:{
        'large-number':'./src/index.js',      // 测试环境代码
        'large-number.min':'./src/index.js'   // 线上环境代码
    },
    output:{
        filename:'[name].js',
        library:'largeNumber',
        libraryTarget:'umd',                  // 用于多环境引入
        libraryExport:'default'               // 去除引用时额外的.default
    },
    optimization:{
        minimize:true,
        minimizer:[
            new TerserPlugin({                // TerserPlugin插件支持压缩ES6代码
                include:/\.min\.js$/,         // 匹配只压缩.min.js文件
            })
        ]
    }
}

构建日志

统计信息stats

Preset Alternative Description
'errors-only' none 只在发生错误时输出
minimal none 只在发生错误或有新的编译时输出
none false 没有输出
normal true 标准输出
verbose none 全部输出

生产环境配置

module.exports = {
    stats:'errors-only'   // 推荐
}

开发环境配置

devServer: {
    contentBase: './dist',
    hot: true,
    stats: 'errors-only'
},

优化命令行的构建日志,使用friendly-errors-webpack-plugin

plugins:[
    new FriendlyErrorsWebpackPlugin()
],
stats:'errors-only'  // 需要搭配这条命令使用

构建配置管理的可选方案

  1. 通过多个配置文件管理不同环境的构建,webpack --config 参数进行控制
  2. 将构建配置设计成一个库
  3. 抽成一个工具进行管理,比如:create-react-app
  4. 将所有的配置放在一个文件,通过--env参数控制分支选择

冒烟测试

  1. 判断是否构建成功
  2. 检测是否有内容输出:
  • 是否有js、css等静态资源文件
  • 是否有HTML文件

持续集成

优点:

  • 快速发现错误
  • 防止分支大幅偏离主干 核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。
    接入Travis CI
  1. https://travis-ci.org/ 使用gitHub账号登录
  2. 在https://travis-ci.org/account/repositories 为项目开启
  3. 项目根目录下新增.travis.yml
    travis.yml文件内容
  • install安装项目依赖
  • script运行测试用例

编写好的构建包发布到npm上

添加用户:npm adduser
升级版本

  • 升级补丁版本号:npm version patch
  • 升级小版本号:npm version minor
  • 升级大版本号:npm version major
    发布版本:npm publish

git规范和Changelog生成

良好的Git commit规范优势:

  • 加快Code Review的流程
  • 根据Git Commit的元数据生成Changelog
  • 后续维护者可以知道Feature被修改的原因

技术方案
20191004115054.png
提交格式要求

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

type代表某次提交的类型,比如是修复一个bug还是增加一个新的feature.所有的type类型如下:

  • build:主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交
  • ci:主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交,
  • docs:仅仅修改了文档,比如README,CHANGELOG,CONTRIBUTE等等
  • feat:新增feature
  • fix:修复bug
  • pref:优化相关,比如提升性能、体验
  • refactor:代码重构,没有加新功能或者修复bug
  • revert:回滚到上一个版本
  • style:仅仅修改了空格、格式缩进、逗号等等,不改变代码逻辑
  • test:测试用例,包括单元测试、集成测试等
  • chore:不属于以上类型的其他类型

安装
npm install husky --save-dev
npm install --save-dev @commitlint/{cli,config-conventional}
配置commitlint.config.js

module.exports = {extends: ['@commitlint/config-conventional']};

配置package.json

{
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }  
  }
}

速度分析:使用speed-measure-webpack-plugin

const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasureWebpackPlugin();
module.exports = smp.wrap({   // 用smp.wrap包裹配置文件
})

体积分析:使用webpack-bundle-analyzer

const BundleAnalyzerPlugin=require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports={
    plugin:[
        new BundleAnalyzerPlugin()
    ]
}

构建完成后会在8888端口展示

加快构建速度:升级webpack和node版本

使用webpack4:优化原因
v8带来的优化(for of替代forEach、Map和Set替代Object、includes替代indexOf)
默认使用更快的md4 hash算法
wepacks AST 可以直接从loader传递给AST,减少解析时间
使用字符串方法替代正则表达式

加快构建速度:多进程/多实例构建

使用webpack官方维护插件:thread-loader解析资源
原理:每次webpack解析一个模块,thread-loader会将它及它的依赖分配给worker线程中

npm install --save-dev thread-loader
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve("src"),
        use: [
          "thread-loader",  // thread-loader要写在loader最前面
          // your expensive loader (e.g babel-loader)
        ]
      }
    ]
  }
}

加快构建速度:多进程/多实例并行压缩代码

使用terser-webppack-plugin 开启parallel参数

const TerserPlugin=require('terser-webpack-plugin');
module.exports={
    optimization:{
        minimizer:[
            new TerserPlugin({
                parallel:4
            })
        ]
    }
}

加快构建速度:进一步分包-预编译资源模块

使用html-webpack-externals-plugin的缺点:每一个基础包都要引入并配置,splitchunk也会再次解析

思路:将reat\react-dom\redux\react-redux基础包和业务基础包打包成一个文件 方法:使用DLLPlugin进行分包,DllReferencePlugin对manifest.json引用
一般需要单独创建webpack.dll.js配置文件

const path = require('path');
const webpack = require('webpack');
module.exports = {
    entry: {
        library: [
            'react',
            'react-dom'
        ]
    },
    output: {
        filename: '[name]_[chunkhash].dll.js',
        path: path.join(__dirname, 'build/library'),
        library: '[name]'
    },
    plugins: [
        new webpack.DllPlugin({
            name: '[name]_[hash]',
            path: path.join(__dirname, 'build/library/[name].json')
        })
    ]
};

在webpack中引用

module.exports={
    plugins:[
        new webpack.DllReferencePlugin({
            manifest:require('./build/library/manifest.json')
        })
    ]
}

在webpack4中性能方面提升不大,但在分包作用中还是有用的

加快构建速度:缓存

目的:提升二次构建速度
缓存思路:

  • babel-loader开启缓存
  • terser-webpack-plugin 开启缓存
  • 使用cache-loader或者hard-source-webpack-plugin(推荐hard-source-webpack-plugin,配置更简单)

加快构建速度:缩小构建目标

目的:尽可能的少构建模块
比如:babel-loader不解析node_modules (各种exclude和include) 减少文件搜索范围

  • 优化resolve.modules配置(减少模块搜索层级)
  • 优化resolve.mainFields配置
  • 优化resolve.extensions配置
  • 合理使用alias
modules.exports={
    resolve:{
        alias:{
            react:path.resolve(__dirname,'./node_modules/react/dist/react.min.js'),
        },
        modules:[path.resolve(__dirname,'node_modules')],
        extensions:['.js'],
        mainFields:['main']
    }
}

treeshaking-删除无用的js和css

无用的CSS如何删除掉?
PurifyCSS:遍历代码,识别已经用到的CSS class
uncss:HTML需要通过jsdom加载,所有的样式通过PostCSS解析,通过document.querySelector来识别在html文件里面不存在的选择器 在webpack中如何使用PurifyCSS?
PurifyCSS-webpack-plugin不再维护所以使用:
purgecss-webpack-plugin+mini-css-extract-plugin配合使用

const PurgecssPlugin = require('purgecss-webpack-plugin');
const PATHS={
    src:path.join(__dirname,'src')
}
    plugins:[
        new PurgecssPlugin({
            paths:glob.sync(`${PATHS.src}/**/*`,nodir:true) // 插件要求路径必须是绝对路径
        })
    ]

图片压缩

使用image-webpack-loader
Imagemin的优点分析

  • 有很多定制选项
  • 可以引入更多第三方优化插件,例如pngquant
  • 可以处理多种图片格式

Imagemin的压缩原理
pngquant:是一款PNG压缩器,通过将图像转换为具有alpha通道(通常比24/32位PNG文件小60-80%)的更高效的8位PNG格式,可显著减少文件大小。
pngcrush: 其主要目的是通过尝试不同的压缩级别和PNG过滤方法来降低PNG IDAT数据流的大小。
optipng:其设计灵感来自于pngcrush。optipng可将图像文件重新压缩为更小尺寸,而不会丢失任何信息。 tinypng:也是将24位png文件转化为更小有索引的8位图片,同时所有非必要的metadata也会被剥离掉

npm i image-webpack-loader -D
rules: [{
  test: /\.(gif|png|jpe?g|svg)$/i,
  use: [
    'file-loader',
    {
      loader: 'image-webpack-loader',
      options: {
        mozjpeg: {
          progressive: true,
          quality: 65
        },
        // optipng.enabled: false will disable optipng
        optipng: {
          enabled: false,
        },
        pngquant: {
          quality: [0.65, 0.90],
          speed: 4
        },
        gifsicle: {
          interlaced: false,
        },
        // the webp option will enable WEBP
        webp: {
          quality: 75
        }
      }
    },
  ],
}]

构建体积优化:动态polyfill

使用Polyfill Service
原理:识别User Agent,下发不同的Polyfill 通过插入https://polyfill.io/v3/polyfill.min.js来实现动态polyfill
国内可以使用阿里的服务http://polyfill.alicdn.com/polyfill.min.js,速度更快 使用方法:

<script src="https://polyfill.io/v3/polyfill.min.js"></script>

配置

px自动转换成rem

需要在html文件中内联引入lib-flexible,一起使用,可以添加exclude去掉不想转换的库,也可以添加/no/的语法去设置某一行样式不进行转换

{
    loader: 'px2rem-loader',
    options: {
        remUnit: 75,   // 如果是750的设计稿
        remPrecision: 8
    }
}

自动添加css前缀

{
    loader: 'postcss-loader',
    options: {
        plugins: () => [
            require('autoprefixer')({
                browsers: ['last 2 version', '>1%', 'ios 7']
            })
        ]
    }

自动清除构建目录产物

const CleanWebpackPlugin = require('clean-webpack-plugin');
plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new CleanWebpackPlugin(),
        new FriendlyErrorsWebpackPlugin()
    ].concat(htmlWebpackPlugins)

文件监听

webpack开启监听模式有两种方式:

  • 启动webpack命令时,带上 --watch 参数
  • 再配置webpack.config.js 中设置watch:true

文件监听的原理分析: 轮询判断文件的最后编辑时间是否变化 某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等aggregateTimeout

module.export={
    // 默认false,
    watch:true,
    // 只有开启监听模式时,watchOptions才有意义
    watchOptions:{
        // 默认为空,忽略文件夹,支持正则,支持数组
        ignored:/node_modules/,
        // 监听到变化发生后会等300ms再去执行,默认300ms
        aggregateTimeout:300,
        // 轮询间隔,默认每秒检查一次变动
        poll:1000
    }
}

热更新

需要webpack-dev-server,配置webpack文件

 devServer: {
        hot: true
 }

devtool分类

alt
这么多模式,到底试用哪个?

  1. 开发环境推荐:cheap-module-eval-source-map
  2. 生产环境推荐:cheap-module-source-map
  3. 相关解释:
  • 大部分情况我们调试并不关心列信息,而且就算 sourcemap 没有列,有些浏览器引擎(例如 v8) 也会给出列信息,所以我们使用 cheap 模式可以大幅提高 souremap 生成的效率。
  • 使用 module 可支持 babel 这种预编译工具(在 webpack 里做为 loader 使用)。
  • 使用 eval 方式可大幅提高持续构建效率,参考webapck devtool速度对比列表,这对经常需要边改边调的前端开发而言非常重要
  • 直接将sourceMap放入打包后的文件,会明显增大文件的大小,不利于静态文件的快速加载;而外联.map时,.map文件只会在F12开启时进行下载(sourceMap主要服务于调试),故推荐使用外联.map的形式。

参考文章

[webpack] devtool配置对比