玩转webpack系列之webpack进阶用法(三)

2,064 阅读7分钟

自动清理构建目录

避免构建前每次都需要手动删除dist,使用clean-webpack-plugin,默认删除output指定的输出目录,使用前安装和引入插件即可。

plugins:[
    new CleanWebpackPlugin()
]

PostCSS插件autoprefixer自动补齐CSS3前缀

CSS3的属性为什么需要前缀

由于众多浏览器的标准并没有统一,目前来看有四种浏览器内核,IE,火狐,谷歌,欧朋

举个例子

.box{
    -moz-border-radius: 10px;
    -webkit-border-radius:10px;
    -o-border-radius:10px;
    border-radius:10px;
}

在远古时期,其他浏览器的支持不是很好的情况下,需要我们手动添加前缀,因为我们平时编写的css文件很多,如果每一行样式我们都需要手动的写一些前缀,无疑是非常大的工作量,那么有没有办法自动补齐CSS3前缀?

PostCSS插件autoprefixer自动补齐CSS3前缀

使用autoprefixer插件

根据Can I Use 规则(caniuse.com/)

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"]
         })
        ]
       }
    }]
  }]
}

browsers可以指定它兼容的版本

移动端CSS px自动转换rem

自从移动设备普及之后,设备分辨率是不断变化的,这样的话对前端开发来说会造成一个比较大的问题,需要不断的进行页面的适配。以前是怎样解决这个问题呢?

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

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

使用px2rem-loader

module:{
    rules:[
    {
    test:/\.less$/,
    use:[
    'style-loader',
    'css-loader',
    'less-loader',
    {
    loader:"px2rem-loader",
    options:{
        remUnit:75,
        remPrecision:8
    }
    }]
  }]
}

通过这个px2rem-loader可以将px转换成rem,转换完成之后我们需要知道一个rem等于多少px,可以使用手淘的lib-flexble(github.com/amfe/lib-fl…),这个lib-flexible会自动根据当前设备的宽高来计算根元素实际font-size的值。

静态资源内联

资源内联的意义

代码层面:

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

请求层面:

  • 减少HTTP网络请求数
  • 小图片或者字体内联(url-loader)

HTML和JS内联

CSS内联

  • 方案一:借助style-loader
loader:'style-loader',
options:{
    //将样式插入到<head>
    inserAt:'top',
    //将所有的style标签合并成一个
    singleton:true,
}
  • 方案二:html-inline-css-weboack-plugin

多页面应用打包通用方案

多页面应用(MPA)概念

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

多页面打包基本思路

每个页面对应一个entry,一个html-webpack-plugin

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

module.exports = {
    entry:{
    index:'./src/index.js',
    search:'./src/search.js'
}

多页面打包通用方案

  • 动态获取entry和设置html-webpack-plugin数量
module.exports = {
    entry:{
        index:'./src/index/index.js',
        search: './src/search/index.js'
    }
}

通过js脚本获取src所有的目录,就可以知道入口文件的数量,在打包的时候就可以动态的设置html-webpack-plugin。

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

以这种同步的方式把文件都查询出来,获取当前的构建的文件下面,匹配src下面一级目录,然后再index.js,这样我们就把所有的entry都动态获取出来了,我们根据entry的数量再动态设置html-webpack-plugin的数量。

使用source map

作用:通过surce map定位到源代码。

  • 大家可以看看阮一峰大神的souce map科普文:(www.ruanyifeng.com/blog/2013/0…)
  • 开发环境开启,线上环境关闭,线上排查问题的时候可以将sourcemap上传到错误监控系统。

source map关键字

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

source map类型

提取页面公共资源

基础库分离

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

SplitChunksPlugin 进行公共脚本分离

webpack4内置的,替代CommonsChunkPlugin插件

chunks参数说明:

  • async 异步引入的库进行分离(默认)
  • initial同步引入的库进行分离
  • all 所有引入的库进行分离(推荐)

SplitChunksPlugin分离基础包

test:匹配出需要分离的包

module.exports = {
    optimization:{
        splitChunks:{
            cacheGroups:{
                commons:{
                 test:/(react|react-dom)/,
                    name:'vendors',
                    chunks:'all'
                }
            }
        }
    }
}

利用SplitChunksPlugin分离页面公共文件

  • miniChunks:设置最小引用次数为2次
  • minuSize:分离的包体积的大小
module.exports = {
    optimization:{
        splitChunks:{
            miniSize:0
            cacheGroups:{
                commons:{
                    name:'commons',
                    chunks:'all'
                    minChunks:2
                }
            }
        }
    }
}

tree shaking(摇树优化)

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

使用: webpack默认支持,在.babelrc里设置modules: false即可. production mode的情况下默认开启

要求:必须是ES6的语法,CJS的方式不支持

DCE(Elimination)

  • 代码不会被执行,不可到达
  • 代码执行结果不会被用到
  • 代码只会影响死变量(只写不读)

Tree-shaking原理

利用ES6模块的特点:

  • 只能作为模块顶层的语句出现
  • import 的模块只能是字符串常量
  • import binding是immutable的

代码擦除:uglify阶段删除无用代码

在编译阶段,tree-shaking知道哪些代码没有用到之后,会将其注释标记,在uglify阶段将其删除掉。

ScopeHoisting使用和原理分析

没有开启scopeHoisting的会产生什么现象?

构建后代码存在大量闭包代码

会导致什么问题?

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

模块转换分析

结论

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

进一步分析webpack的模块机制

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

scope hoisting原理

原理:将所有的模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突

scope hoisting使用

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

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

代码分割和动态import

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

适用的场景:

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

懒加载JS脚本的方式

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

如何使用动态import?

安装babel插件

npm install @babel/plugin-syntax-dynamic-import--save-dev

ES6:动态import(目前还没有原生支持,需要babel转换)

{
    "pulings":["@babel/plugin-syntax-dynamic-import"],
    ...
}

webpack打包组件和基础库

webpack打包库和组件

webpack除了可以用来打包应用,也可以用来打包js库

实现一个大整数加法库的打包

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

库的目录结构和打包要求

打包输出的库名称

  • 未压缩版 large-number.js
  • 压缩版 large-number.min.js
|-/dist
    |-large-number.js
    |-large-number.min.js
|-webpack.config.js
|-package.json
|-index.js
|-/src
    |-index.js

如何将库暴露出去?

  • library:指定库的全局变量
  • library:支持库引入的方式
output:{
    filename:"[name].js",
    library:"largeNumber",
    libraryExport:"default",
    libraryTarget:"umd"
}

优化构建命令行的显示日志

了解统计信息stats

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

使用friendly-errors-webpack-plugin

  • success:构建成功的日志提示
  • warning:构建警告的日志提示
  • error:构建报错的日志提示 status设置成errors-only
module.exports = {
plugins:[
    new FriendlyErrorsWebpackPlugin()    
],
    status:'errors-only'
};

构建异常和中断处理

如何判断是否成功

每一次构建完成后输入 echo $?获取错误码,如果错误码不为0,说明这次构建是失败的,同时也可以获取到错误信息

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

如何主动捕获并处理构建错误?

  • compiler在每次构建结束后会触发done这个hook
  • proce.exit 主动处理构建报错
plugins:[
    function(){
        this.hooks.done.tap('done', (stats) => {
            if (stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf('--watch') =a -1)
            {
                console.log('build error');
                process.exit(1);
            }
        }
    }
]

通过stats.compilation获取到是否存在errors,如果存在代表这次构建是失败的, 失败的话会抛出一个build error出来,同时通过process.exit把错误码抛出来。

写在最后

  • 笔者最近在学习webpack,会像以学习笔记的方式跟大家交流分享。
  • 会持续更新,欢迎点赞关注。