chst365's blog chst365's blog
首页
  • Git
  • 网络
  • 操作系统
  • 浏览器
  • webpack
  • JavaScript
  • TypeScript
  • 性能
  • 工程化
  • React
  • 编程题
  • React技术揭秘
  • 算法
  • Node
  • 编码解码
  • NodeJS系列
  • Linux系列
  • JavaScript系列
  • HTTP系列
  • GIT系列
  • ES6系列
  • 设计模式系列
  • CSS系列
  • 小程序系列
  • 数据结构与算法系列
  • React系列
  • Vue3系列
  • Vue系列
  • TypeScript系列
  • Webpack系列
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

chst365

DIV工程师
首页
  • Git
  • 网络
  • 操作系统
  • 浏览器
  • webpack
  • JavaScript
  • TypeScript
  • 性能
  • 工程化
  • React
  • 编程题
  • React技术揭秘
  • 算法
  • Node
  • 编码解码
  • NodeJS系列
  • Linux系列
  • JavaScript系列
  • HTTP系列
  • GIT系列
  • ES6系列
  • 设计模式系列
  • CSS系列
  • 小程序系列
  • 数据结构与算法系列
  • React系列
  • Vue3系列
  • Vue系列
  • TypeScript系列
  • Webpack系列
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 浏览器

  • webpack

    • 一、基础篇:webpack与构建发展简史
    • 二、基础篇:webpack 基础用法
    • 三、基础篇:webpack 进阶用法
      • 1. 自动清理构建目标产物
        • 当前构建时的问题
        • 通过 npm scripts 清理构建目录
        • 自动清理构建目录
      • 2. PostCSS 插件 autoprefixer 自动补齐 CSS3 前缀
        • CSS3 的属性为什么需要前缀
        • PostCSS
      • 3. 移动端 CSS px 自动转换成 rem
        • 浏览器的分辨率
        • CSS 媒体查询实现响应式布局
        • rem
        • 移动端 CSS px 自动转换成 rem
      • 4. 静态资源内联
        • 4.1 资源内联的意义
        • 4.2 HTML 和 JS 内联
        • 4.3 CSS 内联
      • 5. 多页面应用打包通用方案
        • 多页面应用(MPA)概念
        • 多页面打包基本思路
      • 6. 使用 sourcemap
        • 使用 source map
        • source map 关键字
        • source map 类型
      • 7. 提取页面公共资源
        • 基础库分离
        • 利用 SplitChunksPlugin 进行公共脚本分离
        • 利用 SplitChunksPlugin 分离基础包
        • 利用 SplitChunksPlugin 分离页面公共文件
      • 8. treeshaking 的使用和原理分析
        • tree shaking (摇树优化)
        • DCE(Elimination)
        • Tree-shaking 原理
      • 9. ScopeHoisting 使用和原理分析
        • 现象:构建后的代码存在大量闭包代码
        • 会导致什么问题?
        • 模块转换分析
        • 进一步分析 webpack 的模块机制
        • scope hoisting 原理
        • scope hoisting 使用
      • 10. 代码分割和动态 import
      • 11. webpack 和 ESLint 结合
        • ESLint 的必要性
        • 行业里面优秀的 ESLint 规范实践
        • 制定团队的 ESLint 规范
        • ESLint 如何执行落地?
      • 12. webpack 打包组件和基础库
        • webpack 打包库和组件
        • 库的目录结构和打包要求
        • 支持的使用方式
        • 如何将库暴露出去?
        • 如何指对 .min 压缩
        • 设置入口文件
      • 13. webpack 实现 SSR 打包
        • SSR 代码实现思路
        • webpack ssr 打包存在的问题
      • 14. 优化构建时命令行的显示日志
        • 如何优化命令行的构建日志
      • 15. 构建异常和中断处理
    • 四、进阶篇:编写可维护的 webpack 构建配置
    • 五、进阶篇:webpack 构建速度和体积优化策略
  • TypeScript

  • 性能

  • 工程化

  • React

  • JavaScript

  • 编程题

  • React技术揭秘

  • 算法

  • 前端
  • webpack
chst365
2020-06-02
目录

三、基础篇:webpack 进阶用法

# 三、基础篇:webpack 进阶用法

# 1. 自动清理构建目标产物

# 当前构建时的问题

每次构建的时候不会清理目录,造成构建的输出目录 output 文件越来越多

# 通过 npm scripts 清理构建目录

rm -rf ./dist && webpack
rimraf ./dist && webpack
1
2

# 自动清理构建目录

避免构建前每次都需要手动删除 dist

使用 clean-webpack-plugin * 默认会删除 output 指定的输出目录

module.exports = {
    entry: {
        app: './src/app.js',
        search: './src/search.js'
    },
    output: {
        filename: '[name][chunkhash:8].js',
        path: __dirname + '/dist'
    },
    plugins: [
        new CleanWebpackPlugin({})
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 2. PostCSS 插件 autoprefixer 自动补齐 CSS3 前缀

# CSS3 的属性为什么需要前缀

-w1452

# PostCSS

使用 autoprefixer 插件 根据 Can I Use 规则(https://caniuse.com/ (opens new window))

module.exports = {
    module: {
        rules: [
            {
                test: /\.less$/,
                use: [
                    'style-loader',
                    'css-loader',
                    'less-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            plugins: () => [
                                require('autoprefixer')({
                                browsers: ["last 2 version", ">1%","iOS 7"]
                                })
                            ]
                        }
                    }
                ]
            }
        ]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 3. 移动端 CSS px 自动转换成 rem

# 浏览器的分辨率

-w1202

# CSS 媒体查询实现响应式布局

缺陷:需要写多套适配样式代码

@media screen and (max-width: 980px) {
    .header {
        width: 900px;
    }
}
@media screen and (max-width: 480px) {
    .header {
        height: 400px;
    }
}
@media screen and (max-width: 480px) {
    .header {
        height: 300px;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# rem

W3C 对 rem 的定义:font-size of the root element rem 和 px 的对比: _ rem 是相对单位 _ px 是绝对单位

# 移动端 CSS px 自动转换成 rem

# 使用 px2rem-loader

# 页面渲染时计算根元素的 font-size 值

  • 可以使用手淘的 lib-flexible 库
  • https://github.com/amfe/lib-flexible (opens new window)
module.exports = {
    module: {
        rules: [
            {
                test: /\.less$/,
                use: [
                    'style-loader',
                    'css-loader',
                    'less-loader',
                    {
                        loader: 'px2rem-loader',
                        options: {
                            remUnit: 75,
                            remPrecision: 8
                        }
                    }
                ]
            }
        ]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 4. 静态资源内联

# 4.1 资源内联的意义

# 代码层面:

  • 页面框架的初始化脚本
  • 上报相关打点
  • css 内联避免页面闪动

# 请求层面:减少 HTTP 网络请求数

  • 小图片或者字体内联(url-loader)

# 4.2 HTML 和 JS 内联

# raw-loader 内联 html

<script>${require('raw-loader!babel-loader!./meta.html')}</script>

# raw-loader 内联 JS

<script>${require('raw-loader!babel-loader!../node_modules/lib-flexible')}</script>

# 4.3 CSS 内联

# 方案一:借助 style-loader

# 方案二:html-inline-css-webpack-plugin

module.exports = {
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: [
                    {
                        loader: 'style-loader',
                        options: {
                            insertAt: 'top',        // 样式插入到 <head>
                            singleton: true,        // 将所有的style标签合并成一个
                        }
                    },
                    'css-loader',
                    'sass-loader'
                ]
            }
        ]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 5. 多页面应用打包通用方案

# 多页面应用(MPA)概念

每一次页面跳转的时候,后台服务器都会给返回一个新的 html 文档,这种类型的网站也就是多页面网站,也叫多页面应用。

# 多页面打包基本思路

# 方案一:每个页面对应一个 entry,一个 html-webpack-plugin

# 缺点:每次新增或删除页面需要改 webpack 配置

module.exports = {
    entry: {
        index: './src/index.js',
        search: './src/search.js'
    }
}
1
2
3
4
5
6

# 方案二: 动态获取 entry 和设置 html-webpack-plugin 数量

# 利用 glob.sync

  • entry: glob.sync(path.join(__dirname, './src/*/index.js'))

# 6. 使用 sourcemap

# 使用 source map

# 作用:通过 source map 定位到源代码

  • source map 科普文:http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html (opens new window)

# 开发环境开启,线上环境关闭

  • 线上排除问题的时候可以将 sourcemap 上传到错误监控系统

# source map 关键字

  • eval: 使用 eval 包裹模块代码
  • source map: 产生.map 文件
  • cheap: 不包含列信息
  • inline: 将 .map 作为 DataURI 嵌入,不单独生成 .map 文件
  • module: 包含 loader 的 sourcemap

# source map 类型

-w754

# 7. 提取页面公共资源

# 基础库分离

  • 思路: 将 react、react-dom 基础包通过 cdn 引入,不打入 bundle 中
  • 方法: 使用 html-webpack-externals-plugin -w700

# 利用 SplitChunksPlugin 进行公共脚本分离

Webpack 4 内置的,替代 CommonsChunkPlugin 插件

chunks 参数说明:

  • async 异步引入的库进行分离(默认)
  • initial 同步引入的库进行分离
  • all 所有引入的库进行分离(推荐)
module.exports = {
    optimization: {
        splitChunks: {
            chunks: 'async',
            minSize: 30000,                             // 抽离公共包最小大小(字节)
            maxSize: 0,                                 // 最大大小
            minChunks: 3,                               // 公共代码出现的次数
            maxAsyncRequests: 5,                        //
            maxInitialRequests: 3,
            automaticNameDelimiter: '~',
            name: true,
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 利用 SplitChunksPlugin 分离基础包

# test: 匹配出需要分离的包

module.exports = {
    optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    test: /(react|react-dom)/,
                    name: 'vendors',
                    chunks: 'all'
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 利用 SplitChunksPlugin 分离页面公共文件

  • minChunks: 设置最小引用次数为 2 次
  • minSize: 分离的包体积的大小
module.exports = {
    optimization: {
        splitChunks: {
            minSize: 0,
            cacheGroups: {
                commons: {
                    name: 'vendors',
                    chunks: 'all',
                    minChunks: 2
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 8. treeshaking 的使用和原理分析

# tree shaking (摇树优化)

概念:一个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到 bundle 里面去,tree shaking 就是只把用到的方法打入 bundle,没用到的方法会在 uglify 阶段被擦出掉。 使用:

  • webpack 默认支持,在 .babelrc 里设置 modules: false 即可
  • production mode 的情况下默认开启 要求:必须是 ES6 的语法,CJS 的方式不支持

# DCE(Elimination)

# 代码不会被执行,不可到达

# 代码执行的结果不会被用到

# 代码只会影响死变量(只写不读)

if(false) {
    console.log('这段代码永远不会执行');
}
1
2
3

# Tree-shaking 原理

利用 ES6 模块的特点:

  • 只能作为模块顶层的语句出现
  • import 的模块名只能是字符串常量
  • import binding 是 immutable 的 代码擦除:uglify 阶段删除无用代码

# 9. ScopeHoisting 使用和原理分析

# 现象:构建后的代码存在大量闭包代码

-w1115

# 会导致什么问题?

  • 大量函数闭包包裹代码,导致体积增大(模块越多越明显)
  • 运行代码时创建的函数作用域变多,内存开销变大

# 模块转换分析

-w1254

结论:

  • 被 webpack 转换后的模块会带上一层包裹
  • import 会被转换成 __webpack_require

# 进一步分析 webpack 的模块机制

-w582

分析:

  • 打包出来的是一个 IIFE(匿名闭包)
  • modules 是一个数组,每一项是一个模块初始化函数
  • __webpack_require 用来加载模块,返回 module.exports
  • 通过 WEBPACK_REQUIRE_METHOD(0) 启动程序

# scope hoisting 原理

原理:将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突 对比:通过 scope hoisting 可以减少函数声明代码和内存开销 -w1099

# scope hoisting 使用

webpack mode 为 production 默认开启 必须是 ES6 语法,CJS 不支持

module.exports = {
    entry: {
        app: './src/app.js',
        search: './src/search.js'
    },
    output: {
        filename: '[name][chunkhash:8].js',
        path: __dirname + '/dist'
    },
    plugins: [
        new webpack.optimize.ModuleConcatenationPlugin()
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 10. 代码分割和动态 import

# 代码分割的意义

对于大的 Web 应用来讲,将所有的代码都放在一个文件中显然是不够有效的,特别是当你的某些代码块是在某些特殊的时候才会被使用到。webpack 有一个功能就是讲你的代码库分割成 chunks (语块),当代码运行到需要他们的时候再进行加载

# 适用的场景

  • 抽离相同代码到一个共享块
  • 脚本懒加载,使得初始下载的代码更小 -w468

# 懒加载 JS 脚本的方式

CommonJS: require.ensure ES6: 动态 import(目前还没有原生支持,需要 babel 支持)

# 如何使用动态 import?

  • 安装 babel 插件
    • npm install @babel/plugin-syntax-dynamic-import --save-dev
  • ES6: 动态 import(目前还没有原生支持,需要 babel 支持)
    {
        "plugins": ["@babel/plugin-syntax-dynamic-import"],
    }
    
    1
    2
    3

# 代码分割的效果

-w810

# 11. webpack 和 ESLint 结合

# ESLint 的必要性

2017 年年 4 ⽉月 13 ⽇日,腾讯⾼高级⼯工程师⼩小明在做充值业务时,修改了了苹果 iap ⽀支付配 置,将 JSON 配置增加了了重复的 key 。代码发布后,有⼩小部分使⽤用了了 vivo ⼿手 机的⽤用户反馈充值⻚页⾯面⽩白屏,⽆无法在 Now app 内进⾏行行充值。最后问题定位是: vivo ⼿手机使⽤用了了系统⾃自带的 webview ⽽而没有使⽤用 X5 内核,解析 JSON 时遇到 重复 key 报错,导致⻚页⾯面⽩白屏。

# 行业里面优秀的 ESLint 规范实践

# Airbnb: eslint-config-airbnb、eslint-config-airbnb-base

# 腾讯:

  • alloyteem 团队 eslint-config-alloy https://github.com/AlloyTeam/eslint-config-alloy (opens new window)
  • ivweb 团队:eslint-config-ivweb https://github.com/feflow/eslint-config-ivweb (opens new window)

# 制定团队的 ESLint 规范

  • 不重复造轮子,基于 eslint:recommend 配置并改进
  • 能够帮助发现代码错误的规则,全部开启
  • 帮助保持团队的代码风格统一,而不是限制开发体验 -w671

# ESLint 如何执行落地?

  • 和 CI/CD 系统集成
  • 和 webpack 集成

# 方案一:webpack 与 CI/CD 集成

-w863

# 本地开发阶段增加 precommit 钩子

  • 安装 husky
    • npm install husky --save-dev
  • 增加 npm script,通过 lint-staged 增量检查修改的文件
"scripts": {
    "precommit": "lint-staged"
},
"lint-staged": {
    "linters": {
        "*.{js,scss}":["eslint--fix","git add"]
    }
}
1
2
3
4
5
6
7
8

# 方案二:webpack 与 ESLint 集成

使用 eslint-loader,构建时检查 JS 规范

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [
                    "babel-loader",
                    "eslint-loader"
                ]
            }
        ]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 12. webpack 打包组件和基础库

# webpack 打包库和组件

webpack 除了可以用来打包应用,也可以用来打包 js 库 实现一个大整数加法库的打包

  • 需要打包压缩版和非压缩版
  • 支持 AMD/CJS/ESM 模块引入

# 库的目录结构和打包要求

打包输出的库名称:

  • 未压缩版 large-number.js
  • 压缩版 large-number.min.js

-w311

# 支持的使用方式

  • 支持 ES module

    import * as largeNumber from 'large-number';
    largeNumber.add('999','1');
    
    1
    2
  • 支持 CJS

    const largeNumbers = require('large-number');
    largeNumber.add('999','1');
    
    1
    2
  • 支持 AMD

    require(['large-number'],function (large-number) {
        large-number.add('999','1');
    })
    
    1
    2
    3
  • 可以直接通过 script 引入

    <!doctype html> <html>
    ...
    <script src="https://unpkg.com/large-number"></script> <script>
    // ...
    // Global variable largeNumber.add('999', '1');
    // Property in the window object window. largeNumber.add('999', '1'); // ...
    </script> </html>
    
    1
    2
    3
    4
    5
    6
    7

# 如何将库暴露出去?

  • library: 指定库的全局变量
  • library: 支持库引入的方式
module.exports = {
    mode: "production",
    entry: {
        "large-number": "./src/index.js",
        "large-number.min": "./src/index.js"
    },
    output: {
        filename: "[name].js",
        library: "largeNumber",
        libraryExport: "default",
        libraryTarge: "umd"
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13

# 如何指对 .min 压缩

通过 include 设置只压缩 min.js 结尾的文件

module.exports = {
    mode: 'none',
    entry: {
        'large-number': './src/index.js',
        'large-number.min': './src/index.js',
    },
    output: {
        filename: '[name].js',
        library: 'largeNumber',
        libraryTarget: 'umd'
    },
    optimization: {
        minimize: true,
        minimizer: [
            new TerserPlugin({
                include: /\.min\.js$/,
            })
        ]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 设置入口文件

# package.json 的 main 字段为 index.js

if (process.env.NODE_ENV === 'production') {
    module.exports = require('./dist/large-number.min.js');
} else {
    module.exports = require('./dist/large-number.js');
}
1
2
3
4
5

# 13. webpack 实现 SSR 打包

# 页面打开过程

-w1193

# 服务端渲染(SSR)是什么?

  • 渲染:HTML+CSS+JS+Data -> 渲染后的 HTML
  • 服务端:
    • 所有模板等资源都存储在服务端
    • 内网机器拉取数据更快
    • 一个 HTML 返回所有数据

# 浏览器和服务器交互流程

![-w1178](https://upload.smart-lzgz.cn/mweb/2020%2007%2019%2015951717338538%2015922313582041%20.jpg)

# 客户端渲染 VS 服务端渲染

-w1059 总结:服务端渲染(SSR)的核心是减少请求

# SSR 的优势

  • 减少白屏时间
  • 对于 SEO 友好

# SSR 代码实现思路

# 服务端

  • 使用 react-dom/server 的 renderToString 方法将 React 组件渲染成字符串
  • 服务端路由返回对应的模板

# 客户端

  • 打包出针对服务端的组件

-w453

# webpack ssr 打包存在的问题

# 浏览器的全局变量(Node.js 中没有 document, window)

  • 组件适配:将不兼容的组件根据打包环境进行适配
  • 请求适配:将 fetch 或者 ajax 发送请求的写法改成 isomorphic-fetch 或者 axios

# 样式问题(Node.js 无法解析 css)

  • 方案一:服务端打包通过 ignore-loader 忽略 CSS 的解析
  • 方案二:将 style-loader 替换成 isomorphic-style-loader

# 如何解决样式不显示的问题?

  • 使用打包出来的浏览器端 html 为模板
  • 设置占位符,动态插入组件 -w427

# 首屏数据如何处理?

  • 服务端获取数据
  • 替换占位符 -w445

# 14. 优化构建时命令行的显示日志

# 当前构建是的日志显示

展示一大堆日志,很多并不需要开发者关注

# 统计信息 stats

-w751

# 如何优化命令行的构建日志

# 使用 friendly-errors-webpack-plugin

  • success:构建成功的日志提示
  • warning:构建警告的日志提示
  • error:构建报错的日志提示

# stats 设置成 errors-only

module.exports = { entry: {
app: './src/app.js',
search: './src/search.js' },
output: {
filename: '[name][chunkhash:8].js', path: __dirname + '/dist'
},
plugins: [
+ new FriendlyErrorsWebpackPlugin()
],
+ stats: 'errors-only' };
1
2
3
4
5
6
7
8
9
10

# 使用效果

-w919

# 15. 构建异常和中断处理

# 如何判断构建是否成功?

在 CI/CD 的 pipline 或者发布系统需要知道当前构建状态 每次构建完成后输入 echo $? 获取错误码

webpack4 之前的版本构建失败不会抛出错误码(error code)

Node.js 中的 process.exit 规范

  • 0 表示成功完成,回调函数中,err 为 null
  • 非 0 表示执行失败,回调函数中,err 不为 null,err.code 就是传给 exit 的数字

compiler 在每次构建结束后会触发 done 这个 hook process.exit 主动处理构建报错

plugins: [ function() {
this.hooks.done.tap('done', (stats) => { if (stats.compilation.errors &&
stats.compilation.errors.length && process.argv.indexOf('- -watch') == -1)
{
console.log('build error');
process.exit(1); }
}) }
]
1
2
3
4
5
6
7
8
#工程化#webpack
上次更新: 2021/09/14, 13:59:36
二、基础篇:webpack 基础用法
四、进阶篇:编写可维护的 webpack 构建配置

← 二、基础篇:webpack 基础用法 四、进阶篇:编写可维护的 webpack 构建配置→

最近更新
01
面试官
03-27
02
this&指针&作用域&闭包
03-27
03
前端
03-27
更多文章>
Theme by Vdoing | Copyright © 2019-2025 chst365 | 豫ICP备17031889号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式