从搭建vue-脚手架到掌握webpack配置(二.插件与提取)

4,069 阅读7分钟

前言

上一期从零构建了一个基础版的vue-cli项目,主要介绍了loader的安装和一些配置项的用法,还给项目添加了less预处理器

上一期的链接-从搭建vue-脚手架到掌握webpack配置(一.基础配置)

本期开始引入常用的插件实现开发环境和生成环境会用到的一些功能,比如热插拔、css样式提取、公共模块提、取代码压缩等等

注意 本教程适用webpack3,并不支持webpack4以上

区分开发与生产环境

很多插件功能是在开发环境(development)用到的但是在生产环境(production)用不到的,反之亦然。比如

-development用到的
  • 热插拔调试
  • 生成html模板
-production用到的
  • 生成html模板
  • css样式提取
  • 公共模块提取
  • JavaScript压缩
  • ......

引用官方的说法 ,区分生产和开发环境有两种方法,如下图

image

第二种方法涉及到二次封装,就像官方vue-cli构建的项目一样,分成了三个配置文件,对目前的我们来说比较复杂,我们使用第一种方法,设置环境变量来区分部署环境。

参考vue-cli生成的简单版工程(webpack-simple),我们发现npm script写得有点奇怪

"scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
  }

在运行webpack命令之前运行了 cross-env NODE_ENV=develpomentproduction,这就是给环境变量赋值的过程,但是单单这样写是无法执行的,我们需要安装一个插件——cross-env

npm install --save cross-env

这样我们就可以在之后运行在node环境的js 文件中访问到这些环境变量,通过process.env对象还能拿到package.json里面的配置信息,这就涉及到node的知识了,不多说。

const env = process.env.NODE_ENV
//获取工程的版本号
const version = process.env.npm_package_version

简单点写,把环境变量的判断直接放到webpack.config.js文件的最下面

const path = require('path')
const webpack = require('webpack')

module.exports = {
    entry:{
        app:'./src/main.js'
    },
    //...
}
/**
 * 生成生产代码的时候才触发
 */
if (process.env.NODE_ENV === 'production') {
    // http://vue-loader.vuejs.org/en/workflow/production.html
    module.exports.plugins = (module.exports.plugins || []).concat([
        new webpack.DefinePlugin({
            'process.env': {
            NODE_ENV: '"production"'
            }
        }),
    ])
  }

如果以后额外的配置项越来越多的话,像上面这样写是不太好合并配置项的,到最后还是要抽离出另一个js文件装载新增或重写的配置项,用webpack-merge中间件合并配置对象。

webpack.DefinePlugin插件是设置全局常量的插件,要记住!赋值的时候记得写成'"production"', 官方对DefinePlugin插件 是这么说的

注意,因为这个插件直接执行文本替换,给定的值必须包含字符串本身内的实际引号。通常,有两种方式来达到这个效果,使用 '"production"', 或者使用 JSON.stringify('production')。

生成html模板

之前根目录下index.html要我们自己引入js资源地址,有新的资源都要手动引入,很麻烦,这时候就会用到HtmlWebpackPlugin 插件,按照index.html作为模板在dist目录下生成带上所有资源的html 文件。

npm install --save-dev html-webpack-plugin

先通过require引入插件,然后在输出对象里面添加plugins属性,数据值类型是数组,数组成员new [插件]()添加插件就行。每个插件都有自己的配置项和规范,可以查 npmjs 或者 他们的官方文档

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')

module.exports = {
    entry:{
        app:'./src/main.js'
    },
    output:{
        path:path.resolve(__dirname,'./dist'),
        filename:"js/[name].js",
    },
    module:{
        rules:[
        //...
        ]
    },
    plugins:[
        new HtmlWebpackPlugin({
            filename:'index.html',
            title:'vue demo',
            template:'./index.html',
        })
    ],
    externals:{
        'jquery':'window.jQuery'
    }
}
说明
  • filename 生成的html的文件名,不填就默认是原文件名
  • title title标签的内容
  • template html模板地址,这里我们用我上一期建在跟目录的index.html

这里有前辈对HtmlWebpackPlugin的详细说明文章

index.html的内容要改一改了,因为webpack打包完之后自动添加资源地址到html文件里,所以我们要删掉原本写上去的script标签

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue demo</title>
</head>
<body>
    <div id="app">
     
    </div>
    <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.js"></script>
</body>
</html>

有人可能会奇怪,这里为什么加了一个cdn的jQuery,因为我要在这里带过一个知识点:有时候我们会有用到cdn加速的库资源,但是不知道怎么在工程中使用。

很简单我们在html模板中直接引入,然后在webpack.config.js配置中加一项“外部引入”(externals)

// webpack.config.js
externals:{
    'jquery':'window.jQuery'
}
//app.vue中引入
import $ from 'jquery'

热替换

web服务器

使用热替换之前当然要先有一个web服务器环境啦,安装webpack-dev-server

npm install --save-dev webpack-dev-server

webpack-dev-server其实是一个独立的插件,但是webpack内置了它的配置项,属性devServer对应的就是它的配置项。

module.exports = {
    entry:{
        app:'./src/main.js'
    },
    output:{
        path:path.resolve(__dirname,'./dist'),
        filename:"js/[name].js",
    },
    devServer:{
        contentBase:"./dist"
    }
}

端口地址什么的都默认 http://localhost:8080/ ,就设置了跟资源目录地址contentBase。 想更深入的去配置可以看官方文档 dev-server。我还真没认真看过,嘻嘻。

热替换插件

热替换就是开发的过程中修改文件内容之后不用频繁刷新页面,修改会自动同步到浏览器中,webpack内部已经有这份插件了,不用安装直接都用就可以。在plugins添加一项 new webpack.HotModuleReplacementPlugin()就ok了

plugins:[
        new HtmlWebpackPlugin({
            filename:'index.html',
            title:'vue demo',
            template:'./index.html',
        }),
        new webpack.HotModuleReplacementPlugin()
    ]

改一下npm scripts

"scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
  }

运行 npm run dev,热部署搞定

以上是开发环境要用到的插件,下面就是生成环境用到的插件了

css文件和vue内样式提取

如果不提取css样式,所有的.css文件和vue内的style都会以style标签的形式被添加到页面的head里面,不利于资源的缓存而且降低了页面的加载速度。

好的,就用extract-text-webpack-plugin插件吧,老规矩安装一下

npm install extract-text-webpack-plugin --save-dev
简单使用

在使用css相关loader之前先用本插件过滤一遍

var ExtractTextPlugin = require("extract-text-webpack-plugin")

module.exports = {
  // other options...
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            css: ExtractTextPlugin.extract({
              use: 'css-loader',
              fallback: 'vue-style-loader' // <- 这是vue-loader的依赖
            }),
            //用了less或者sass的地方都要用上哦
            'less': ExtractTextPlugin.extract({
                use:[
                    'css-loader',
                    'less-loader'
                ],
                fallback:'vue-style-loader'
            })
          }
        }
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin("styles/style.css")
  ]
}

vue内部的style需要先抽取出来,所以要在fallback属性上添加预先的加载器 'vue-style-loader','vue-style-loader'是vue-loader自带的哦,如果运行时报错的话那就手动install一下他吧。

生成多文件

我一般的习惯是把外部引入的css文件认为是可以复用的,而vue内的style是每个页面都不一样的要另外生成的,所以我建了两ExtractTextPlugin实例分别抽取样式到两个文件里。

const path = require('path')
const webpack = require('webpack')
const ExtractTextPlugin = require("extract-text-webpack-plugin")
const ExtractRootCss = new ExtractTextPlugin({filename:'styles/root.css',allChunks:false});
const ExtractVueCss = new ExtractTextPlugin({filename:'styles/[name]/style.css',allChunks:true});

module.exports = {
    //other options...
    module:{
        rules:[
        //...
            {
                test:/\.css$/,
                //这里用的ExtractRootCss
                use:ExtractRootCss.extract({
                    fallback:'style-loader',
                    use:['css-loader']
                })
            },
            {
                test:/\.less$/,
                //这里用的ExtractRootCss
                use:ExtractRootCss.extract({
                    fallback:'style-loader',
                    use:[
                        'css-loader',
                        'less-loader'
                    ]
                })
            },
            {
                test:/\.vue$/,
                loader:'vue-loader',
                options:{
                    loaders:{
                        //这里用的ExtractVueCss
                        'css': ExtractVueCss.extract({
                            use: 'css-loader',
                            fallback: 'vue-style-loader' // <- 这是vue-loader的依赖,所以如果使用npm3,则不需要显式安装
                          }),
                        //这里用的ExtractVueCss
                        'less':
                        ExtractVueCss.extract({
                            use:[
                                'css-loader',
                                'less-loader'
                            ],
                            fallback:'vue-style-loader'
                        })
                    },
                }
            },
        ]
    },
    plugins:[
        new HtmlWebpackPlugin({
            filename:'index.html',
            title:'vue demo',
            template:'./index.html',
        }),
        ExtractRootCss,//填入插件实例,复用的css
        ExtractVueCss,//记得按顺序填入,vue内的css
        new webpack.HotModuleReplacementPlugin(),
    ]
}

这就是ExtractTextPlugin插件生成多个文件的方法。你也可以按照自己的习惯去配置。

公共代码提取

在多页面或者多入口的时候(entry设了不只一个),不同的模块(chunks)会多次引入一样的资源模块(module,也就是import引入的js文件),还有vue等库的代码,以上这些复用的代码最好是可以独立出来,一方面方便缓存,一方面减少包的体积。

CommonsChunkPlugin插件就是解决这一问题的,它从属于webpack.optimize对象所以也是不用安装的。具体使用如下

new webpack.optimize.CommonsChunkPlugin({
    name: 'vender',
    minChunks:2
})

minChunks参数可以是number类型,填2 就是说有2个chunk以上用到的公共块就会被打包的vender.js里面。minChunks也可以传一个方法,返回值是boolean类型.

(chunk可以简单理解为entry属性设置的入口而生成的整条关系树,所以到目前为止本项目也只有一个chunk,就是'app',当然插件生成的vender也是一个chunk。对初学者来说就这样理解吧,用多了自然会有概念)

既然只有一个chunk 那就先抽取公用库中的代码吧,如vue包中的代码。把代码放到生产环境判断里面哦~

/*生成生产代码的时候才触发*/
if (process.env.NODE_ENV === 'production') {
    module.exports.plugins = (module.exports.plugins || []).concat([
        new webpack.DefinePlugin({
            'process.env': {
            NODE_ENV: '"production"'
            }
        }),
        //抽取从node_modules引入的模块,如vue
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vender',
            minChunks:function(module,count){
                var sPath = module.resource;
                // console.log(sPath,count);
                //匹配 node_modules文件目录
                return sPath &&
                    /\.js$/.test(sPath) &&
                    sPath.indexOf(
                        path.join(__dirname, 'node_modules')
                    ) === 0
            }
        })
    ])
  }

这是中文文档上的介绍 commons-chunk-plugin

这是一个好心人总结的各种配置情况下打包的结果 segmentfault.com/a/119000000…

其他插件

源码映射

因为重构和压缩后的代码不利于debug,所以我们先要开启source map功能,在webpack配置里面添加一项devtool,如下

module.exports = {
    //entry: ...
    devtool: '#eval-source-map'
}
if (process.env.NODE_ENV === 'production') {
    module.exports.devtool = '#source-map'
}

eval-source-map是开发环境用的源码映射,source-map是生成环境用的源码映射

官方对 devtool的介绍在这里

阮一峰老师对 source map 的介绍在这里

js代码压缩

css文件在 build(抽取和装载)的同时已经进行了简单的压缩,所以下面主要是对js代码的压缩,也就是常常的UglifyJs(丑化js),webpack自带了UglifyJsPlugin插件,在plugins上启用就行。

new webpack.optimize.UglifyJsPlugin({
    sourceMap: true,//开启源码映射
    compress: {
        warnings: false//去到警告
    }
}),

但是以上的用法是webpack1.0遗留下来的,用的旧版的UglifyJs,他的使用说明也在wepack1.0的文档里。你可以有手动安装uglifyjs-webpack-plugin,引入最新的UglifyJs

/* npm install -save-dev uglifyjs-webpack-plugin */

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

new UglifyJsPlugin({
  uglifyOptions: {
    compress: {
      warnings: false
    }
  },
  sourceMap: true
})

webpack3.0中文文档对该插件的说明 在这里

官方文档的介绍 在这里

webpack1.0迁移插件

loader-options-plugin 和其他插件不同。它的用途是帮助人们从 webpack 1 迁移至 webpack 2。官方说明

new webpack.LoaderOptionsPlugin({
    minimize: true
}),

运行构建试一下

好了到目前为止大部分会用到的插件都引入到了webpack配置里面,构建一下试试。

完整webpack.config.js的代码在这里 pan.baidu.com/s/1jKnDSYa

npm run dev

dev

npm run build

image

发现uglifyJs报错,是因为我们没有配置babel的翻译器和编译规则,篇幅有限babel的配置说明放到下一期。

解决方法:在根目录下创建文件.babelrc,内容如下

{
  "presets": [
    ["env", {
       "modules": false 
    }]
  ]
}

安装babel-preset-env,npm install --save-dev babel-preset-env

然后再build,没问题了

image

打包后的目录结构如下

image

唠叨几句

想要深入了解每个插件的具体用法,定制自己的需求一定要多点去参考文档和资料。为了方便大家我已经在教程中每一个插件的下面给了大量的链接,可以说省去了大家百度的时间,突然感觉自己好细心。

官方文档也不需要全部都看,用到什么看什么,要什么功能配置就重点看那部分就好,等到有时间再简要的过一遍文档。

下期预告

到目前为止,整个工程可以说完全可用了。样式抽离,公共提取,压缩都用到了,对比一下vue init webpack-simple project-name构建的简单工程,会发现我们比它的功能还完整,有没有一点成就感呢?

很可惜,没想到讲插件用了这么长的篇幅,还是没有提到postcss和babel的配置,下一期开始简要提一下这些,然后我们继续优化构建过程,让他可以适应多入口多页面的开发。想要了解以后的内容可以关注哦~~

第三期已更新:从搭建vue-脚手架到掌握webpack配置(三.多页面构建)

参考