05月09, 2018

webpack4配置笔记

一眨眼的事,webpack已经更新到4.6了。

alt

个人感觉也差不多可以入坑了。不过怕就怕再过个半年,来个5.x,但愿5.x和4.x区别不大吧。

webpack1和2相差挺大的,2和3相差不大,然后3和4还是挺大的,主要还是4把commonPlugin给拿掉了,换到了另外一个配置中。

起步

之前我们使用webpack命令,只要安装webpack模块即可,现在还要再安装一个webpack-cli

npm install webpack webpack-cli -D

零配置

这个只是针对比较简单的项目。

webpack会去找src下的index.js,最后编译到dist目录。

alt

mode参数主要是用来控制压不压缩,当执行命令为:

npx webpack --mode development

生成的dist/main.js是一个不压缩的文件。默认不传,它的值为production,会压缩文件。

基本配置

const path = require("path");

module.exports = {
    entry: "./src/index.js", 
    output: {
        filename: "bundle.js",
        path: path.join(__dirname, "dist")
    }
}

比较简单,就是入口配置、出口配置。需要注意的是:output里面的path必须是绝对路径。

entry这个有讲究了,它允许接收:

  • 字符串
  • 数组
  • 对象

第一个是一个入口,后面两个是多个入口。那么对象跟数组的区别是啥?

数组可以这样写:

alt

但如果对象也是类似写法:

alt

那么就会报错:

alt

当然解决很容易,只要用name(也就是entry里面的key)区别分文件名就行了:

const path = require("path");

module.exports = {
    entry: {
        index: "./src/index.js",
        a: "./src/a.js"
    },
    output: {
        filename: "[name].bundle.js",
        path: path.join(__dirname, "dist")
    }
}

我们也可以指定md5文件,写法如下:

[name].bundle.[hash:5].js

:5是表示截取5位。

问题来了,除了hash,好像还有什么chunkhash、contenthash,它们之间的区别是啥?

在网上找到一篇文章:Webpack中hash与chunkhash的区别,以及js与css的hash指纹解耦方案

hash是compilation对象计算所得,而不是具体的项目文件计算所得。所以以上配置的编译输出文件,所有的文件名都会使用相同的hash指纹

chunkhash是根据具体模块文件的内容计算所得的hash值,所以某个文件的改动只会影响它本身的hash指纹,不会影响其他文件。

dev-server

显然我们不可能每次webpack打包一次,再刷新html页面。这个最好是能在内存里面帮我们编译好,然后实时刷新页面(或者hot-reaload),所以就有了webpack-dev-server这个模块。

在不配置content的情况下,默认编译的js文件是在根目录下的。我们可以先简单点,在根目录下创建一个html,引用index.bundle.js

alt

我们都知道有一个hot-reload,那么来试试吧:

{
    "script": {
         "start": "webpack-dev-server --hot"
    }
}

然而页面还是刷新了。正确的姿势应该是在入口地方多加一段代码:

if (module.hot) {
    // 这里可以实现热更新
    module.hot.accept();
}

当然在accept里面可以接收变更的文件,这就类似react的hot-reload了,然而配置要更麻烦一些,在入口那里也要加一个插件才行。

配置到文件中

上面的写法是在命令行中的配置,我们也可以将其写到webpack.config.js中:

alt

hot参数加了的话,就需要添加上HotModuleReplacementPlugin,不然会报错。

host参数通常为0.0.0.0,因为我们有时候需要通过IP进行访问,上面写成"loacalhost",那么IP就访问不了。

前阵子,碰到一个bug,是报:Invalid Host header,同事用的是自定义域名的方式,解决的方案需要加上一个额外的字段:

{
    disableHostCheck: true
}

解决 Webpack "Invalid Host Header"

插件

这里列举一些常用的插件:

clean-webpack-plugin

用来清理文件夹的,譬如我们可以拿它来清理dist目录,写法如下:

const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    mode: "development",
    entry: {
        ...
    },
    output: {
        ...
    },
    devServer:{
        ...
    },
    plugins: [
        new CleanWebpackPlugin(["./dist"])
    ]
}

HotModuleReplacementPlugin

这是webpack自带的,和devServer的hot参数配合使用。当然如果是配置在命令行--hot,则不需要加这个插件

const webpack = require("webpack")
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    mode: "development",
    entry: {
        ...
    },
    output: {
        ...
    },
    devServer:{
        ...,
        hot: true
    },
    plugins: [
        new CleanWebpackPlugin(["./dist"]),
        new webpack.HotModuleReplacementPlugin(),
    ]
}

html-webpack-plugin

在webpack配置中,我们可以将output的生成文件,加上md5,但这样一来,我们得手动改html文件,所以就有了html-webpack-plugin,它能够帮我们插入script及link标签。

需要注意的点是:hotreload不能和chunkhash同时使用。

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

module.exports = {
    mode: "development",
    entry: {
        ...
    },
    output: {
        ...
    },
    devServer:{
        ...
    },
    plugins: [
        ...,
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: './index.html',
            chunks:['index'], // 多入口时需要用到
            hash: true, // 插入的文件后面加一段随机数
        })
    ]
}

HtmlWebpackPlugin参数是一个option对象,它不是一个数组,所以当有多个html时,需要new多次。

hash的作用在于,url后面多了一串随机数:

alt

purifycss-webpack

移除未使用的css,github地址:purifycss-webpack

不过我个人觉得这个插件已经一年没更新了,怕是没人维护了。

DefinePlugin

这也是webpack自身提供的插件。

比如我们有这个一个场景,测试环境用A地址,线上环境用B地址。那么我们就可以利用这个插件结合Node_ENV来实现,然后配多个scripts。

const webpack = require('webpack');

process.env.NODE_ENV = "development"; // 实际不可能写死的,通过script来配置不同的环境

module.exports = {
    mode: "development",
    entry: {
        ...
    },
    output: {
        ...
    },
    devServer:{
        ...
    },
    plugins: [
        ...,
        new webpack.DefinePlugin({
            __DEV__: process.env.NODE_ENV === 'development'
        }),
    ]
}

然后我们就可以在自己的业务代码中使用__DEV__这个变量了。

extract-text-webpack-plugin

安装时,得npm install extract-text-webpack-plugin@next,因为4.0.0版本目前处于beta。

用来抽取css,这个插件用法会放在loder里面说明,因为这个代码得和loader一起使用才行。

copy-webpack-plugin

用来拷贝静态文件。用法如下:

const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
    mode: "development",
    entry: {
        ...
    },
    output: {
        ...
    },
    devServer:{
        ...
    },
    plugins: [
        ...,
        new CopyWebpackPlugin([{
            from:'./src/public',
            to:'public'
        }]),
    ]
}

BannerPlugin

webpack自带的一个插件,用来给代码添加版权信息。用法如下:

{
    plugins: [
        ...,
        new webpack.BannerPlugin('henryzp\'s copyRight'),
    ]
}

alt

ProvidePlugin

webpack自带的一个插件,自动加载模块,而不必到处 import 或 require 。

{
    plugins: [
        ...,
        new webpack.ProvidePlugin({
           $: 'jquery',
        }),
    ]
}

随后在业务代码中加一行console.log("$")

alt

loader

wepack最最核心的就是,将所有的资源给转成js文件,这些是需要不同的loader来进行处理的,比如说css-loader、babel-loader、file-loader等。

语法如下:

module.exports = {
    mode: "development",
    entry: {
        ...
    },
    output: {
        ...
    },
    module: {
        rules: [
            // 这里放一个个的loader对象
        ]
    }
    devServer:{
        ...
    },
    plugins: [
        ...
    ]
}

loader对象大概是长下面这样的:

alt

有以下常见的key:

  • test (匹配规则)
  • use (数组,里面放一个个的loader,执行顺序从右到左)
  • exclude (排除什么文件夹)

css相关配置

以less为例的话,先要安装less、less-loader、css-loader、style-loader模块。

配置如下:

{
    test: /\.less$/,
    use: [
        "style-loader",
        "css-loader",
        "less-loader"
    ]
}

use数组中的每一项都可以改写成如下的形式:

{
    loader: "style-loader",
    options: {}
}

上面几个loader的作用依次为:less-loader将代码转成css、css-loader用于解析(可以用来做css-moudle、压缩等)、style-loader则将解析后的样式嵌入js代码。

到了生产环境,我们不可能还是style的形式,通常都会抽成一个css。这个就得借助于上面提到的extract-text-webpack-plugin插件:

module.exports = {
    mode: "development",
    entry: {
        ...
    },
    output: {
        ...
    },
    module: {
        rule: {
            {
                test: /\.less$/,
                use: ExtractTextWebpackPlugin.extract({
                    use: [
                        "css-loader",
                        "less-loader"
                    ]
                })
            }
        }
    }
    devServer:{
        ...
    },
    plugins: [
        ...,
        new ExtractTextWebpackPlugin({
            filename: "css/index.css"
        })
    ]
}

注意上面没有style-loader了。

这样一来的话,就得写两个webpack配置了。如果想写成一个的话,就得写成这样的:

alt

当然有人可能会提到postcss,其实比较简单,只要再安装一个postcss-loader,然后在根目录下加一个postcss.config.js,大概写法如下:

module.exports = {
    plugins:[require('autoprefixer')]
}

postcss有点类似babel,是css解析器,然后它需要一个个的插件来处理不同的事。比如我们不用less,光用postcss的插件,也可以实现less提供的嵌套等功能。

html-withimg-loader

这个是指在html文件中有用到某张图片,该图片有在入口文件中被引用。

具体配置如下:

{
    test: /\.html/,
    use: 'html-withimg-loader'
},
{
    test: /\.(png|gif|jpg)$/,
    use: [{
        loader: 'url-loader',
        options: {
            limit: 5,
            outputPath: 'images/'
        }
    }]
},

expose-loader

The expose loader adds modules to the global object

文档地址:这里

{
    test:/jquery/,
    use:[{
        loader:'expose-loader',
        options:'$'
    }]
},

注意: 模块必须在你的 bundle 中被 require() 过,否则他们将不会被暴露。

alt

resolve

这个对象可以用来起别名、新增扩展后缀等功能。

module.exports = {
    mode: "development",
    entry: {
        ...
    },
    output: {
        ...
    },
    module: {
        ...
    },
    resolve:{
        // 别名
        alias:{
            '指定的名称':path.resolve(__dirname,'文件路径')
        },
        // 省略后缀名
        extensions:[' ','.js','.json','.css'],
        modules:['node_modules','lib'],
        //mainFields:['./a.js'] // 默认package.json的main是文件的入口 可以更改
    },
    devServer:{
        ...
    },
    plugins: [
        ...
    ]
}

抽取公共代码

module.exports = {
    mode: "development",
    entry: {
        ...
    },
    output: {
        ...
    },
    module: {
        ...
    },
    optimization:{
        splitChunks:{
            cacheGroups:{
                commons: {
                    chunks: 'initial',
                    name: 'commons',
                    minChunks: 2,
                    maxInitialRequests: 5,
                    minSize: 0
                },
                vendor:{ // 抽离第三插件
                    test:/node_modules/,
                    chunks:'initial',
                    name:'vendor',
                    priority:10
                }
            }
        }
    },
    resolve:{
        ...
    },
    devServer:{
        ...
    },
    plugins: [
        ...
    ]
}

当然这样改了之后,下面的htmlPlugin里面也要加上这两个才行:

alt

在掘金上有一篇文章是关于这个参数讲解的,大家可以看看:webpack4.0打包优化策略(三)

在webpack3的时候,我依旧记得有一个bug,对Webpack的hash稳定性的初步探索,就是业务改了之后,vendor.js的hash值也会变化。

然后上面的方式,我测试了是OK了,也就是说不需要再加入HashedModuleIdsPlugin

externals

除了上面的抽取公共代码之外,方案可能还有dllPlugin或者直接引用script的方式。

个人不太喜欢dllPlugin的方式,觉得太过于复杂,而且在多个spa应用中,公共代码这一块比较适合script的方式。

配置:

module.exports = {
    mode: "development",
    entry: {
        ...
    },
    output: {
        ...
    },
    module: {
        ...
    },
    optimization:{
        ...
    },
    resolve:{
        ...
    },
    devServer:{
        ...
    },
    plugins: [
        ...
    ],
    externals: {
        jquery: "jQuery"
    }
}

然后在html文件中用script引入jQuery即可。

导成类库

module.exports = {
    mode: "development",
    entry: {
        ...
    },
    output: {
        ...
        library: '这里填你的导出类名',
        libraryTarget: 'umd',
        umdNamedDefine: true
    },
    module: {
        ...
    },
    optimization:{
        ...
    },
    resolve:{
        ...
    },
    devServer:{
        ...
    },
    plugins: [
        ...
    ],
    externals: {
        ...
    }
}

不过话说写库什么的,最终用rollup的会比较多一些,如React就是用的rollup。

结语

花了好几天的时间,整理了一下webpack4的基本配置。

不过像优化,用什么happyPack,以及压缩使用多线程啊,并没有提到。

个人觉得webpack4在速度上确实快了一些。MD,今天上github去看,发现已经更新到4.8.1了,这速度真的杠杠的!

本文链接:www.my-fe.pub/post/webpack-4-basic-config-note.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。