webpack进阶(三)

·  阅读 620
webpack进阶(三)

引言

本文主要介绍

  • sourcemap的分析与使用
  • 页面公共资源的提取
  • Tree Shaking的原理与使用

1.sourcemap

Source map是一个信息文件,里面储存着位置信息。有了它,出错的时候,开发工具将直接显示原始代码,而不是转换后的代码。一般建议开发环境时开启(快速定位错误信息,提升开发效率),线上环境关闭(避免暴露业务代码)。 感兴趣的可以看看 阮一峰老师的Source Map详解 对于如此实用的soucemap,webpack也帮我们开放了自定义配置选项——devtool。

1.1 了解webapck的devtool

devtool是webpack的一个options。用于控制是否以及如何生成 source map。默认不开启。

const path = require('path');

module.exports = {
  devtool: 'none', // SourceMap
  entry: './src/index.js',  // 入口文件
  output: {
    filename: 'bundle.js',  // 文件名
    path: path.resolve(__dirname, 'dist')  // 文件夹
  }
}
复制代码

2.2 devtool的可选值

这里直接截取 官方文档的表格说明:

image.png

表格中: `+++` 非常快速, `++` 快速, `+` 比较快, `o` 中等, `-` 比较慢, `--` 慢
复制代码

其中一些值适用于开发环境,一些适用于生产环境。

看到表格的同志不要被这么多的可选值给吓唬住啦。其实这些选项大多都是由这几个关键字组合而成的,咱只需要记住关键字的意思就可以猜出个大概了~

关键字代表含义
eval使用eval包裹模块代码
source-map产生.map文件
cheap不包含列信息
inline将.map作为DataUrl嵌入,不单独生成.map文件
module包含loader的sourcemap

2.3 devtool最佳实践

对于开发环境,通常希望更快速的 source map,需要添加到 bundle 中以增加体积为代价,但是对于生产环境,则希望更精准的 source map,需要从 bundle 中分离并独立存在。 一般情况下,推荐开发环境使用

devtool: 'cheap-module-eval-source-map',
复制代码

生产环境使用

devtool: 'cheap-module-source-map',
复制代码

2.页面公共资源的提取

2.1 externals的配置与使用

对于不常更新的第三方库,我们可以通过配置externals使其脱离webpack打包,而是采用CDN的方式引入。这样既可以加快打包速度,减少bundle体积,又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用(一般都以import方式引用使用)。这里以React,ReactDOM为例

方式一: 使用插件HtmlWebpackExternalsPlugin
npm i HtmlWebpackExternalsPlugin -D
复制代码

在webpack的plugins中使用HtmlWebpackExternalsPlugin

const HtmlWebpackExternalsPlugin = require('HtmlWebpackExternalsPlugin');
module.exports = {
    ...
    plugins: [
        new HtmlWebpackExternalsPlugin({
            externals: [
                {
                    module: 'react',
                    entry: 'https://unpkg.com/react@16/umd/react.production.min.js',
                    global: 'React'
                },
                {
                    module: 'react-dom',
                    entry: 'https://unpkg.com/react-dom@16/umd/react-dom.production.min.js',
                    global: 'ReactDOM'
                }
            ]
        }),
        new HtmlWebpackPlugin({
            meta: {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'},
            favicon: './src/images/favicon.ico',
            scriptLoading: 'blocking',
            template: path.join(__dirname, './src/index.html'),
            minify: true
        })
    ]
}
复制代码

执行打包命令,在浏览器中访问dist/index.html,可以看到index.html中动态添加了如下代码:

image.png 观察下使用插件前后两次打包文件体积大小变化

使用前: image.png 使用后: image.png

方式二:配置externals

首先在index.html页面引入静态资源

<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
复制代码

然后配置externals

const HtmlWebpackExternalsPlugin = require('HtmlWebpackExternalsPlugin');
module.exports = {
    ...
    externals: {
        'react': 'React',
        'react-dom': 'ReactDOM'
    }
}
复制代码

执行打包命令,在浏览器中访问dist/index.html,正常访问。同上,React与ReactDOM没有被webpack打包。

2.2 分离基础包

当我们多个模块使用共同的资源时,为了减少打包体积,可以使用webpack4内置的 splitChunksPlugin 将公共资源提取出来。

const HtmlWebpackExternalsPlugin = require('HtmlWebpackExternalsPlugin');
module.exports = {
    ...
    optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    test: /(react|react-dom)/,
                    name: 'vendors',
                    chunks: 'all'
                }
            }
        }
    }
}
复制代码

打包后可以看到,react与react-dom已经被分离出来了

image.png

更多配置项戳 split-chunks-plugin配置项文档

3. Tree Shaking的原理与使用

3.1 什么是tree shaking

tree shaking——摇树。把webapck形象成一棵树,入口文件就是树的主干,各个依赖模块就是树的枝杈。”摇树“顾名思义,就是把webpack这颗大树上的没有用的枝杈给摇掉。它的本质是消除无用的js代码。无用代码消除在广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为DCE(dead code elimination)。

webpack2及以后的版本,内置了tree shaking的功能。

3.2 如何触发tree shaking

什么样的代码会被认定为是无用的不影响输出的代码并被webpack摇掉呢?

  • 1 永远不会到达(被执行)的代码
  • 2 只写不读的代码
  • 3 执行的结果永远不会被用到的代码

例如:

    // 永远不被执行的代码
    if (false) {
        console.log('我还在吗?');
    }
    // 只写不读的代码
    let unusedVar = 'var 我还在吗'

    // 执行的结果永远不被用到的代码
    const Search = () => {
    // 永远不被执行的代码
    if (false) {
        console.log('我还在吗?');
    }
    // 只写不读的代码
    let unusedVar = 'var 我还在吗'

    // 执行的结果永远不被用到的代码
    const fn = () => {
        return 'return 我还在吗'
    }

    
    useEffect(() => {
        fn()
    }, [])

    console.log('我还在吗?')
    const [comp, setComp] = useState();
    const clicked = () => {
        import('./test.js').then(text => {
            setComp(text.default);
        })
    }
    return <div>
        <div className="search-text less" onClick={clicked}>Search Text</div>
        {comp && comp}
    </div>
}
复制代码

用上面的代码,打包后看看webapck的输出文件:

image.png

以上无用代码都被shaking掉了~

3.3 tree shaking原理

tree shaking主要是利用ES6 模块的特点,在代码的预编译阶段,将无用的代码找出并标记,然后在uglify阶段将标记的无用代码擦除。因为需要再预编译阶段就确定哪些是无用代码,所以tree shaking只能在静态modules下工作。ECMAScript 6 模块加载是静态的,因此整个依赖树可以被静态地推导出解析语法树。

ES6的特点:

  • 只能作为模块顶层的语句出现
  • import的模块名只能是字符串常数,不能是变量
  • import后不能修改

这些特点保证了模块是静态的。

注意:为了利用 tree shaking 的优势, 你必须使用 ES2015 模块语法(即 import 和 export)并且确保没有编译器将 ES2015 模块语法转换为 CommonJS 的(顺带一提,这是现在常用的 @babel/preset-env 的默认行为,详细信息请参阅文档)。

分类:
前端
收藏成功!
已添加到「」, 点击更改