webpack进阶用法

523 阅读14分钟

自动清理构建目录产物

当前构建时的问题

每次构建的时候,我们不会通过webpack自动清理构建目录,而是每次构建之前手动的删除构建目录,其实这样的话,每次构建前不清理目录的话,就会造成构建目录输出的output文件越来越多,针对这个问题利用了npm script 做了目录清理,在每次构建之前npm script里面增加前置操作 rm -rf ./dist 做完目录删除之后,接下来再运行webpack。

通过 npm scripts 清理构建⽬录

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

这个方法,并不是特别优雅,有没有一个更加好的办法呢?答案是有的。
我们其实可以借助webpack插件的功能,在webpack里面刚好提供了一个比较好的插件,叫做clean-webpack-plugin。可以通过这个插件达到避免每次构建前需要手动的删除这个dist。

⾃动清理构建⽬录

避免构建前每次都需要⼿动删除 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()
};

接下来我们具体看一下这个插件的用法。
首先我们安装一下这个插件

打开项目,引入clean-webpack-plugin,然后把它加到插件数组里面去。

同样把这段代码复制一份到webpack.dev.js
可以用npm run build 测试一下。

PostCSS插件autoprefixer自动补齐CSS3前缀

来看一下在webpack中对css的一些增强的功能。
大家其实都知道,移动设备的浏览器众多,因此需要面对很多兼容性问题,但其实有些兼容性问题我们在构建阶段可以尽量避免的。像css3的前缀的问题。

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

这里面其实不得不说的是由于浏览器的标准并没有完全统一,目前来看还是有以下四种浏览器内核。

举个例子

我们给一个盒子,设置border-radius这个属性,这个其实是经常存在的,这个时候呢在几年前其他浏览器对它支持不是很好的时候,我们需要补全各个浏览器的前缀,比如:

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

因为我们平时编写的css样式其实有很多,如果每一个不兼容的样式,都写前缀的话其实是一种很浪费时间的过程,有没有什么办法,我们还能正常的编写这些样式,自动的去补全这些前缀呢,答案是有的,在webpack里面我们通过autoprefixer⾃动补⻬CSS3 前缀。

PostCSS 插件 autoprefixer ⾃动补⻬ CSS3 前缀

使⽤ autoprefixer 插件
根据 Can I Use 规则( caniuse.com/

autoprefixer这个其实是css的后置处理器,与less,sass不同,less,sass通常是css的预处理,预处理器就是在是在打包前置去处理,autoprefixer是样式处理好之后,代码最终生成之后,在对它进行后置处理。我们来看一下autoprefixer的用法。
这个通常也是和PostCSS loader一起使用的,PostCSS loader其实它的功能是很强大的,除了样式补全之外呢,它还可以支持CSS Modules等。

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

下面看一下css自动补全的例子

当我们没用autoprefixer时候运行一下npm run build

可以看到打包后的样式并没有自动补齐其他的前缀。
下面实现一下样式补齐的功能。
首先我们安装一下postcss-loader 和autoprefixer插件。

接下来我们在把代码加进去。

打包npm run build


打包后发现已经自动补全了。

移动端CSS px自动转换成rem

浏览器分辨率

不同机型分辨率不同,对前端开发造成对问题就是需要进行页面的适配。以前我们怎么做页面的适配呢,其实有一种比较常用的方式就是使用css的媒体查询去实现响应式的布局。

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

那么有没有比较好的方式呢,其实还是有的 就是rem。

rem 是什么?

自从css3出现之后,css3里面提出了rem的单位。
W3C 对 rem 的定义: font-size of the root element 也就是根元素font-size的大小。也就是说rem是一个相对的单位。怎么在webpack里面去使用rem呢。或者说我们也希望在编写代码时候安装设计稿的规范编写代码。常见的是375或者750宽度的设计稿。编写代码之后我们还是按照px单位去写,通过构建工具自动的将其转化成rem。

移动端 CSS px ⾃动转换成 rem

使⽤ px2rem-loader
⻚⾯渲染时计算根元素的 font-size 值

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

这里面主要是使用px2rem-loader,我们通过px2rem-loader来将px转换成rem。px转换成rem之后,我们需要知道1rem等于多少px。这个时候我们需要在页面打开的时候动态的去计算根元素font-size的值。这个时候我们可以利用手机淘宝的比较成熟的方案,lib-flexible,它会自动的根据当前设备的宽高来计算根元素实际的font-size的值。接下来在代码里面看一下效果。首先安装一下px2rem-loader

接下来再来安装一下lib-flexible(动态计算根元素的font-size也就是说rem的单位)

然后我们在webpack配置里面将px2rem加进来。option传入两个参数,remUnit是rem相对px的转化的单位。这里面我们设计75,就代表1rem等于75px。比较适合750的设计稿,750px对应10rem。另外一个对应的单位,remPrecision,这个是px转换成rem时候小数点的位数。

开始构建,构建完之后px会自动转化成rem。 npm run build之后

接下来我们就要将根元素的相对的rem单位计算出来,也就是在不同设备分辨率下的font-size的大小。这个时候我们需要使用lib-flexible,要支持lib-flexible我们需要手动将代码引入进来。

npm run build 打开打包后的html 这样页面就有自动计算根元素font-size的能力。

可以看到字体随屏幕分辨率变化而变化。 这一小节其实我们也遇到一个问题,对于一些库如果我们想内敛到html里面去,目前我们构建还不支持,在webpack我们怎么做代码资源的内敛呢,比如我们怎么将打包出来的样式内敛到html里面去,怎么样将一个js库直接内敛进来,怎么去内敛图片,字体等等。接下来看下一节怎样解决的。

静态资源内联

资源内敛的意义

资源内敛是什么含义呢,就是说我们的代码,css代码,js代码怎么样内敛到html里面去,或者说把一些图片,字体等等怎么样内敛到代码里面去,这个有什么意义呢,资源内敛的意义主要从两个方面来看,从代码的层面来看呢,其实我们都知道,我们打开一个页面,其实要做一些初始化的工作,比如说,以上一小节为例,像rem的计算,在刚刚打开页面的时候就需要计算根元素font-size的大小,这个是需要计算的,另外的话其实还有很多上报相关的打点,还有一些css初始化,css加载完成,js初始化,js加载完成等等上报点的一些代码,其实都需要内敛到html里面去,另外的话还有一些好处是,像我们通常情况会把页面的css或者首屏的css的内容会内敛到html里面去,这样加载完html也已经加载完css了,避免页面的闪动,这个时候是代码层面的体验和初始化时候的内容,从请求层面来看,资源内敛可以减少http请求的数量,比如我们把小的图片比如小于10k的图片或者字体等等我们可以把它内敛到代码里面去,这样的话会减少整个请求的数量,小图片和字体内敛我们在之前已经讲解到了,用url-loader,我们给这个url-loader传递一个参数limit,小于limit会自动内敛进去,这一小节我们看一下,代码层面的内敛是怎么做的。

html和js内敛

这里面我们可以直接使用row-loader,我们可以直接在我们的html页面,比如,现在要把一段html内敛进去,html片段,比如像一个我们在开发手机端的页面的时候,它可能有大段的meta信息,这个时候其实我们每个页面都是需要的,那这个时候我们会把这个meta片段拆出来假如名叫meta.html,然后每个页面把这个内敛进来,这个时候我们就可以通过raw-loader把它内敛进来就可以了,对于一些js脚本的话,我们也可以通过raw-loader把它内敛进来,比如我们的lib-flexible的这个库,我们可以把它内敛进来,如果内敛进来的js,是我们写的业务代码,里面存在着es6代码,这个时候我们除了raw-loader之外还需要加上babel-loader,内敛之前对它进行转化,转化完成之后在通过raw-loader来引入进来,raw-loader最新版本有点儿问题,用0.5的版本。

css内敛

加下来要演示一下资源的内敛要怎么做
这里面准备了两个文件,一个是meta.html

大家做手机端h5的页面开发,在html头部需要有一些meta信息,接下来就把meta.html引入进来,直接采用raw-loader,接下来安装一下raw-loader。

其实raw-loader它的功能很简单,就是说它是直接读取一个文件,然后把文件的内容返回String,直接把对应的内容String插入到位置。
在模版index.html里面引入一下这个raw-loader。指定文件路径${ require('raw-loader!./meta.html')}
将原来的search.html的meta删掉,引入meta.html

接下来我们在把rem计算的脚本引入进来,删掉上节的直接复制写入script标签里面的。需要把引入的放到script标签里面去。raw-loader加一个babel-loader

打包看一下
已经倒入进来了。

看一下网页源代码

已经成功倒入进来了。

多页面应用打包通用方案

多⻚⾯应⽤(MPA)概念

每⼀次⻚⾯跳转的时候,后台服务器都会给返回⼀个新的 html ⽂档, 这种类型的⽹站也就是多⻚⽹站,也叫做多⻚应⽤。

多页面与单页面相比而言其实最直接的就是,多页面毫无疑问页面发布上线之后有很多的入口,一个页面一个业务,单页面应用通常是我们会把所有的业务放到一个大入口里面去,不同的子业务还是同样一个url,只不过后面的hash会发生一些变化,多页面应用有什么优势呢,总结来看的话,他有两点优势,第一点优势就是多页面应用,相当于每个页面之间他是解耦的,另外一个优势,多页面应用对SEO更加友好。
然后我们来看一下,在webpack里面要怎样打包这个多页面。

多⻚⾯打包基本思路

其实在前面的基础也已经学习到了多页面应用的基本的打包,比如当时我们有两个页面,一个是首页index.html,另一个是搜索页search.html,当时我们的做法是把首页和搜索页的entry都设置好,然后增加了两个html-webpack-plugin,就是一个entry对应一个html-webpack-plugin,但是我们后面要加一个新的页面,比如我们要加一个列表页或者是详情页,或者是我们要删除一个页面,这个时候呢,我们是需要手动的去修改构建的脚本的,比如我们增加页面的时候,我们需要添加新的entry,并且添加与之对应的html-webpack-plugin,这样其实显而易见的是这种做法其实是不太好的,那么有没有更加通用化的方案呢?

多⻚⾯打包通⽤⽅案

这里面我们就通过程序的思维,每次能够动态的去获取某个目录下面指定的入口文件,这里面需要有个约定,我们把所有的页面都放到src下的目录下面,比如首页是src/index/index.js这个入口文件,搜索页是在src/search/index.js,这样的话我们就有个很好的办法,通过js脚本去获取src所有的目录,获取到目录之后就可以知道入口文件的数量,然后在打包的时候去动态的设置html-webpack-plugin,相比自己去写js脚本,其实在webpack里面有个更加通用的做法就是通过glob这个库,我们可以看一下glob的用法,www.npmjs.com/package/glo…
glob的原理类似linus操作系统下的文件的通配匹配的概念,glob我们只需输入类似正则的匹配规则,然后他就会把匹配到的文件信息,或者目录的内容全部都返回出来,然后再对返回的内容进行操作就可以了。
这里面我们可以利用glob.sync,glob.sync就是说以同步的方式把这些文件都查询出来,glob.sync我们获取当前的构建的目录下面,__dirname就是运行脚本时候所在的目录也就是项目的根目录,根目录下面的./src这个文件夹下面,匹配src下面所有的一级的目录./src/* 匹配完之后在index.js这样的话我们就把所有的entry动态的获取出来了,接下来我们根据获取出来的entry数量动态的去获取html-wbpack-plugin的数量,其实就可以解决问题,接下来我们来看一下具体的实现。

多页面打包具体实现

页面结构在重新梳理一下,遵循前面我们提到的规则,新建一个index目录

把关于index的资源(index.js,index.html)都放进去。然后新建search文件夹,我们把搜索页面的资源也都放进去,接下来我们让它们的模版文件都叫index.html,入口文件都叫index.js,修改一下search.html,search.js

这样的话我们的准备工作就已经完成了,接下来我们就进去构建代码的编写,首先,我们还是需要安装一下glob这个库。

这里面的话我们写一个函数,设置多页面打包,这个函数需要返回entry,和这个html-webpack-plugin所以的话我们先定义一下entry,entry的话是一个对象,htmlWebpackPlugins是一个数组,最后是需要把entry,htmlWebpackPlugins返回回来,这个函数的功能就是动态的设置htmlWebpackPlugin和entry,然后我们来获取一下这里面的entryFiles,通过glob来获取,引入glob,const glob = require('glob'),获取的目录是项目目录下的src匹配所有的文件,里的index.js
const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'));

我们可以打印一下entryFiles看一下效果

编译一下npm run build

这里面的entryFiles其实就已经打印出来了,正好满足我们的要求,根据这个entryFiles来匹配出我们的页面,这个时候我们需要把src和index.js中间的内容匹配出来,接下来我们就要动态的设置entry,通过这个Object.keys来获取这个entryFiles这个数组每一项然后对其进行处理,entryFile就是这个数组每一项具体的内容,这样的话entryFile就已经拿到了。

拿到entryFile之后呢,接下来我们就需要设置这个entry,我们需要先获取这个page name,也就是说src和index.js中间的文件夹名,这个时候呢其实我们可以写一个正则匹配的规则,把这个pageName匹配到,写一个正则,src开头,末尾是index.js中间匹配出来。entryFile.match(/src/(.*)/index.js/);

获取到pageName之后呢,接下来我们就设置一下这个entry

接下来我们在设置一下具体的htmlWebpackPlugin,把他的数组加进去,htmlWebpackPlugins.push() 这个时候其实我们就把之前的具体的拷贝过来,然后对里面的内容稍微修改一下就好了,

这样的话我们函数就编写好了,调用函数接收返回来的 entry, htmlWebpackPlugin,替换之前entry

将之前写的htmlWebpackPlugin删掉,将接收到的动态获取的htmlWebpackPlugin加进来

打包运行看一下

可以看到这里面代码已经编译出来了。

可以看到页面都正常编译出来了。那么后面我们要添加新页面,也就完全不需要修改webpack的配置就动态的支持多页面打包。