从零开始的 webpack4

鉴于自己 webpack 知识一过久了就忘记的尿性,今天闲来无事又敲一遍,顺便做个记录

github 链接先放!


众所周知,webpack 以配置麻烦闻名于世,像我这种记忆力越来越差的人想起他就觉得头皮发麻

先连接好水管

  1. 创建文件夹 npm init -y
    • 这个没啥好说…
  2. npm i -D webpack webpack-cli webpack-dev-server
    • webpack4 之后需要单独安装多一个 webpack-cli
    • webpack-dev-server 这个大佬是起一个本地服务器的,你懂我意思吧
  3. 创建一个 webpack.config.js 文件,用来写配置(对的,用来写你写完,一个月不写就会全部忘光光的配置!所以你到底零配置了什么…)

  4. 好了,我们的文件开写,先把几个神仙请出来
    1
    2
    3
    4
    5
    6
    7
    8
    module.exports = {
    entry:'', //入口
    output:{}, //出口
    module:{}, //处理对应模块
    plugins:[], //对应插件
    devServer:{}, //开发服务器设置
    mode:'development' //模式配置
    }
    • entry 就是入口,它霸道的掌握了整个 webpack 的门口,什么神仙都是从这里进去的,你可以使用字符串语法或者对象语法来定义入口
      1
      2
      3
      4
      5
      6
      7
      8
      9
      module.exports = {
      //一般这么写
      entry:'./src/index.js', //单页面入口
      entry:{ //多页面应用入口
      index:'./src/index.js',
      main:'./src/main.js'
      },
      ...
      }
    • output 则是 webpack 出口,对应入口来说,整个 webpack 过程你可以理解成人体消化系统,而入口进去的食物,总将会变成 X 输出,从 entry 通向 output
    • module 是指 webpack 所处理的模块,比如 ES6、LESS\SASS\CSS、图片等在项目中的种种东西,需要对应的 loader 来对正则匹配的文件进行处理,如 转换 ES6 要用到 babel-loader 来处理 /\.js$/ 的文件
      • loader 则是用于对模块的源代码进行转换的小工具。
    • plugin 插件存在的目的在于解决 loader 无法实现的其他事,比如可以用一些插件来对代码进行压缩
    • devServer 这个则是配置一个静态的服务器,它默认自动刷新。
    • mode 告知 webpack 使用相应环境的内置优化,一般是 developmentproduction


5. 配一下执行文件先…

  • 找到你的 package.json 然后写进去,写完你就可以快乐的在 cmd 里面快乐的 npm run xxx
  1. 然后我们创建一个 src 文件夹,创建个 index.js。就随便写句话吧
    1
    2
    //index.js
    console.log('webpack 配置简单')
  2. 然后我们回到 webpack.config.js 将入口,出口配置好,然后 npm run build 一下,你就能看到文件夹中出现 dist 文件夹,里面有个 index.js 辣。就这样我们完成了第一次 webpack 之旅
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //webpack.config.js
    const path = require('path')
    module.exports = {
    // entry:'', //入口
    entry:'./src/index.js', //单页面入口
    // entry:{ //多页面应用入口
    // index:'./src/index.js',
    // main:'./src/main.js'
    // },
    output:{
    filename: 'index.js', // 打包后文件名
    path: path.resolve('dist') // 打包后目录,需为绝对路径
    }, //出口
    // module:{}, //处理对应模块
    // plugins:[], //对应插件
    // devServer:{}, //开发服务器设置
    // mode:'development' //模式配置
    }


到这里大家应该对webpack有了个小的了解,其实他就是条 ‘管道’ 我们将各种文件都交给他,最后他输出我们配置好的输出文件。

其实比较类似人体的消化系统…你给他吃各种食物,最后输出…你懂我意思吧

给管道添加各类处理程序

webpack 的魅力在于它能处理开发项目中的一切你需要它处理的模块,只要你给他装了相应的模块配置,它就能给你干一切脏活累活。

html

在打包 html 的时候,一般我们在实际开发中都会用一个 html 文件来做一切页面的入口,我们要来实现 html 的打包功能, 需要一个模板来实现,各种引用好路径的 html 。

这里就需要一个很常用的插件 html-webpack-plugin 来帮我们把 html 搞出来

  1. 装插件,先 npm 装一下
    1
    npm i html-webpack-plugin -D
  2. 创建一个index.html 作为模板使用(当然你也可以不用模板)
  3. 接下来要往 webpack.config.js 管子里装设备了
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    const path = require('path')
    //首先引入插件,插件是一个类,使用的时候需要先 new
    const HtmlWebpackPlugin = require('html-webpack-plugin')

    module.exports = {
    entry:'./src/index.js', //单页面入口
    output:{
    filename: 'index.js', // 打包后文件名
    path: path.resolve('dist') // 打包后目录,需为绝对路径
    }, //出口
    // module:{}, //处理对应模块
    plugins:[
    new HtmlWebpackPlugin({
    template:'./src/index.html', // 打包目标模板
    // title:'我被打包了',// 不用 template 时候生效
    // filename:'bundle.html', // 换个文件名
    })
    ], //对应插件
    // devServer:{}, //开发服务器设置
    // mode:'development' //模式配置
    }
  4. 写完,打包,一气呵成。不出意外你会得到一个这样的文件

多页面开发配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// webpack.config.js
const path = require('path')
//首先引入插件,插件是一个类,使用的时候需要先 new
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
entry:{
index:'./src/index.js',
main:'./src/main.js',
}, //多页面入口
output:{
filename: '[name].js', // 打包后文件名
path: path.resolve('dist') // 打包后目录,需为绝对路径
}, //出口
// module:{}, //处理对应模块
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html', // 打包目标模板
filename:'index.html',
chunks:['index'], //块 表示 你注入的 js
}),
new HtmlWebpackPlugin({
template:'./src/main.html', // 打包目标模板
filename:'main.html',
chunks:['main'], //块 表示 你注入的 js
})
], //对应插件
// devServer:{}, //开发服务器设置
// mode:'development' //模式配置
}


css

处理完 html 我们接着来处理 css,处理 css 文件,则需要在配置文件中配置 module ,当然需要指定的 loader 来搭配一起辅助。

一般处理 css 模块的 loader 是这两个大佬

  • css-loader : 很单纯的处理 cssloader
  • style-loader :将处理好的 css-loader 的样式代码 插入文件中

东西说完,我们开搞

  1. 装东西
    1
    npm i css-loader style-loader -D
  2. 创建对应文件,写东西,代码引入
    1
    2
    3
    //index.js
    import './css/style-css.css';
    console.log('webpack 配置简单')
    1
    2
    3
    4
    /* style-css.css */
    body{
    background-color: #000;
    }
  3. 写配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // webpack.config.js
    ...
    //首先引入插件,插件是一个类,使用的时候需要先 new
    const HtmlWebpackPlugin = require('html-webpack-plugin')

    module.exports = {
    ...
    module:{
    rules:[
    {
    test: /\.css$/, //匹配查找 css 文件
    use:['style-loader', 'css-loader'] //从右往左解析 style-loader ← css-loader
    }
    ]
    }, //处理对应模块
    ...
    }

  4. 打包查看效果

    这样子css就被打包进去js文件了,品如跟洪世贤在一起了,这显然不是我们想要的鸭。这时候就需要人来帮我们来挖墙脚。

分手大师

我们现在要用一个插件来实现我们棒打鸳鸯的目的,这个插件可以是 mini-css-extract-plugin 或者 extract-text-webpack-plugin

先说 mini-css-extract-plugin

mini-css-extract-plugin这个插件据说是为了 webpack4 而生的,那我们就来跟跟潮流~

  1. 先装
    1
    npm i -D mini-css-extract-plugin
  2. 再写,这里的原理跟 html-webpack-plugin 是差不多的。将 style-loader 替换成 MiniCssExtractPlugin.loader ,然后在用插件生成文件。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // webpack.config.js
    ...
    const MiniCssExtractPlugin = require('mini-css-extract-plugin')

    module.exports = {
    ...
    module:{
    rules:[
    {
    test: /\.css$/, //匹配查找 css 文件
    // use:['style-loader', 'css-loader'] //从右往左解析 style-loader ← css-loader
    use:[MiniCssExtractPlugin.loader, 'css-loader'] //从右往左解析 style-loader ← css-loader
    }
    ]
    }, //处理对应模块
    plugins:[
    ...
    new MiniCssExtractPlugin({
    filename:'css/[name].css'
    })
    ], //对应插件
    ...
    }
  3. 然后得到

extract-text-webpack-plugin

extract-text-webpack-plugin 则是一直以来 webpack 分拆 jscss 的一个插件

  1. 1
    2
    //@next 版本支持 webpack4
    npm i -D extract-text-webpack-plugin@next
  2. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    // webpack.config.js
    ...
    const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
    const CleanWebpackPlugin = require('clean-webpack-plugin')
    //需要装 cross-env 来做跨平台的兼容
    // 将package.json 中的 scripts "build": "cross-env NODE_ENV=production webpack"
    // 这样代码就能知道我们是处于什么环境打包的
    const devMode = process.env.NODE_ENV !== 'production' //判断是否生产环境
    const cssLoaders = [
    'css-loader'
    ]

    module.exports = {
    ...
    module:{
    rules:[
    {
    test: /\.css$/, //匹配查找 css 文件
    // use:['style-loader', 'css-loader'] //从右往左解析 style-loader ← css-loader
    // use:[
    // // 这个插件暂时不支持 HMR ,开发环境中还是使用 style-loader
    // devMode ? 'style-loader' : {
    // loader:MiniCssExtractPlugin.loader,
    // options:{
    // // 这里可以指定一个 publicPath
    // // 默认使用 webpackOptions.output中的publicPath
    // // publicPath:''
    // }
    // },
    // 'css-loader'
    // ] //从右往左解析 MiniCssExtractPlugin.loader ← css-loader
    use: devMode ? ['style-loader',...cssLoaders] : ExtractTextWebpackPlugin.extract({
    fallback: "style-loader",
    use:cssLoaders
    })
    }
    ]
    }, //处理对应模块
    plugins:[
    ...
    // 将所有的CSS提取到一个文件中
    // new MiniCssExtractPlugin({
    // filename:'css/[name].css'
    // }),
    new ExtractTextWebpackPlugin({
    filename:'css/[name].css'
    })
    ], //对应插件
    ...
    }
  3. 得到

其实两者用着感觉差别并不大,如果有需要分开打包 css 文件的话则后者更好用点,毕竟前者还未支持;前者用着更加简单舒服点

LESS 、SASS 、 postCss 这群小婊砸们怎么融入进来呢?

LESS

处理 LESS 文件,那必须是要使用 lessless-loader 模块啦。什么,你难道觉得不需要咩

  1. 1
    npm i -D less less-loader
  2. 写,这里其实已经万变不离其宗了,需要注意的是 loader 解析方向
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // webpack.config.js
    ...
    const cssLoaders = [
    'css-loader',
    'less-loader'
    ]

    module.exports = {
    ...
    module:{
    rules:[
    ...
    {
    test: /\.less$/, //匹配查找 css 文件
    // use:['style-loader', 'css-loader', 'less-loader'] //从右往左解析 style-loader ← css-loader
    use:[
    devMode ? 'style-loader' : {
    loader:MiniCssExtractPlugin.loader,
    options:{
    }
    },
    ...cssLoaders
    ] //从右往左解析 MiniCssExtractPlugin.loader ← css-loader ← less-loader
    }
    ]
    }, //处理对应模块
    ...
    }

  3. 得到
SASS

SASS 文件的处理其实跟 LESS 差不太多,需要装 sass-loadernode-sass 来帮忙解析

  1. 1
    npm i -D sass-loader node-sass
  2. 写,其实跟 LESS 一个套路辣
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // webpack.config.js
    ...
    const sassLoaders = [
    'css-loader',
    'sass-loader'
    ]

    module.exports = {
    ...
    module:{
    rules:[
    ...
    {
    test: /\.scss$/, //匹配查找 scss 文件
    // use:['style-loader', 'css-loader', 'sass-loader'] //从右往左解析 style-loader ← css-loader ← sass-loader
    use:[
    devMode ? 'style-loader' : {
    loader:MiniCssExtractPlugin.loader,
    options:{
    }
    },
    ...sassLoaders
    ] //从右往左解析 MiniCssExtractPlugin.loader ← css-loader ← sass-loader
    }
    ]
    }, //处理对应模块
    ...
    }

  3. 得到
PostCSS

PostCSS 是 一款使用插件转换 CSS 的工具,里面有很多实用的插件,比如 Autoprefixer

  1. 1
    npm i -D postcss-loader autoprefixer
  2. 写,先添加一个postcss.config.js,用来写 postcss-loader 的配置,也可以在 webpack.config.js 里面用对象方式写,不过一般是单独一个文件写,之后的配置需要注意的问题是 postcss-loader ,需要插在 css-loader 之前。
    1
    2
    3
    4
    5
    6
    7
    8
    //postcss.config.js
    module.exports = {
    plugins: [
    require('autoprefixer')({
    browsers: ['last 10 Chrome versions', 'last 5 Firefox versions', 'Safari >= 6', 'ie> 8']
    })
    ]
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // webpack.config.js
    const sassLoaders = [
    'css-loader',
    'postcss-loader',//需要插在css-loader之前
    'sass-loader'
    ]
    module.exports = {
    ...
    module:{
    rules:[
    ...
    {
    test: /\.scss$/, //匹配查找 scss 文件
    use:[
    devMode ? 'style-loader' : {
    loader:MiniCssExtractPlugin.loader,
    options:{
    }
    },
    ...sassLoaders
    ]
    //从右往左解析 MiniCssExtractPlugin.loader ← css-loader ← postcss-loader ← sass-loader
    }
    ]
    },
    ...
    }
  3. 得到

到这里 CSS 模块的处理的差不多了,整理一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// webpack.config.js
const path = require('path')
//首先引入插件,插件是一个类,使用的时候需要先 new
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
//需要装 cross-env 来做跨平台的兼容
// 将package.json 中的 scripts "build": "cross-env NODE_ENV=production webpack"
// 这样代码就能知道我们是处于什么环境打包的
const devMode = process.env.NODE_ENV !== 'production' //判断是否生产环境
const styleLoader = 'style-loader'
const miniLoader = {
loader:MiniCssExtractPlugin.loader,
options:{}
}
// 这个插件暂时不支持 HMR ,开发环境中还是使用 style-loader
const extractLoader = devMode ? styleLoader : miniLoader
const cssLoader = 'css-loader'
const postcssLoader = 'postcss-loader'
const lessLoader = 'less-loader'
const sassLoader = 'sass-loader'
const cssLoaders = [
extractLoader,
cssLoader,
postcssLoader,
]
const lessLoaders = [
...cssLoaders,
lessLoader,
]
const sassLoaders = [
...cssLoaders,
sassLoader,
]

module.exports = {
entry:'./src/index.js', //单页面入口
output:{
filename: '[name].js', // 打包后文件名
path: path.resolve('dist') // 打包后目录,需为绝对路径
}, //出口
module:{
rules:[
{
test: /\.css$/, //匹配查找 css 文件
// style-loader/miniCss-loader ← css-loader ← postcss-loader
use: cssLoaders
},
{
test: /\.less$/, //匹配查找 less 文件
// style-loader/miniCss-loader ← css-loader ← postcss-loader ← less-loader
use: lessLoaders
},
{
test: /\.scss$/, //匹配查找 scss 文件
// style-loader/miniCss-loader ← css-loader ← postcss-loader ← sass-loader
use:sassLoaders
}
]
}, //处理对应模块
plugins:[
new CleanWebpackPlugin(), //清理文件夹
new HtmlWebpackPlugin({
template:'./src/index.html', // 打包目标模板
// title:'我被打包了',// 不用 template 时候生效
// filename:'bundle.html', // 换个文件名
}),
// 将所有的CSS提取到一个文件中
new MiniCssExtractPlugin({
filename:'css/[name].css'
}),
// new ExtractTextWebpackPlugin({
// filename:'css/[name].css'
// })
], //对应插件
// devServer:{}, //开发服务器设置
// mode:'development' //模式配置
}

js

现在来处理 js 辣,都 2019 年了,现在谁还不认识个 ES6

是的,低版本浏览器就不认识,所以这就需要一个很牛逼的东西来转义我们的ES6ES5

那就是babel(爸爸)!

  1. 1
    npm i babel-core babel-loader babel-preset-env babel-preset-stage-0 -D
  2. 写,这里我们需要写一个配置文件 .babelrc ,然后写下处理的对象
    1
    2
    3
    4
    // .babelrc
    {
    "presets": ["@babel/preset-env"] // 从右向左解析
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // webpack.config.js
    ...
    module.exports = {
    ...
    module:{
    rules:[
    ...
    {
    test:/\.js$/,
    use: 'babel-loader',
    include: /src/, // 只转化src目录下的js
    exclude: /node_modules/ // 排除掉node_modules,优化打包速度
    }
    ]
    },
    ...
    }
  3. 这样配置完,我们就可以在项目里面写 ES6 代码了,webpack 会帮我们转义成 ES5

图片文件

上面那些大佬已经被我们安排的明明白白的,目前还有个图片文件处理需要我们来肝。

处理图片需要用到 file-loaderurl-loaderhtml-withimg-loader

  1. 1
    npm i file-loader url-loader html-withimg-loader -D
  2. 写,这里需要注意的是路径问题,需要在打包成文件的时候指定一下路径
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    // webpack.config.js
    const miniLoader = {
    loader:MiniCssExtractPlugin.loader,
    options:{
    publicPath: '../'
    }
    }
    ...
    module.exports = {
    ...
    module:{
    rules:[
    ...
    {
    test:/\.(jpe?g|png|gif)$/,
    use:[
    {
    loader: 'url-loader',
    options: {
    limit: 8192, // 小于8k的图片自动转成base64格式,并且不会存在实体图片
    outputPath: 'images/' // 图片打包后存放的目录
    }
    }
    ]
    },
    {
    test: /\.(htm|html)$/,
    use: 'html-withimg-loader' // html里面所有的img链接
    },
    {
    test: /\.(eot|ttf|woff|svg)$/,
    use: 'file-loader'
    }
    ]
    },
    ...
    }
  3. 效果

雪碧图

处理雪碧图用的 postcss-sprites 是基于 postcss-loader 的插件

  1. 1
    npm i -D postcss-sprites
  2. 写,这里需要一个 postcss-sprites-config.js 文件来写 postcss-sprites 的配置,也可以写在 postcss-config.js 里面。然后在 postcss-config.js 补上我们的配置,这样就能去合成雪碧图了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    /** postcss-sprites-config.js
    * 这里的项目结构是
    * —src
    * ——icons
    * ———icons-@1x
    * ———— *.png
    * ———icons-@2x
    * ———— *.png
    * */
    var postcss = require('postcss');
    // 合法的散列图path 查找 /src/icons/*/*.png
    const REG_SPRITES_PATH = /\/icons\/(.*?)\/.*/i
    // 合法的retina标识 @*x
    const REG_SPRITES_RETINA = /@(\d+)x\./i
    // split
    const SPLIT = true
    const SPLIT_REG = /\?/ig
    const RETINA = true
    const OUTPUT_PATH = "./dist/sptite"

    module.exports = {
    spritePath: OUTPUT_PATH,
    //过滤 除了 icons 以外 的图片链接
    //需返回一个Promise对象
    filterBy: (image)=> {
    return REG_SPRITES_PATH.test(image.url) ? Promise.resolve() : Promise.reject()
    },
    //分组 分出 1 / 2 / 3 / n倍 图
    groupBy: (image) => {
    let groups = null;
    let groupName = '';
    let module = image.originalUrl.split(/\?/)
    image.url = module[0]
    if(module.length > 1){
    //分模块 ? 跟模块名
    groupName = module[1] + '-'
    }
    if (SPLIT) {
    groups = REG_SPRITES_PATH.exec(image.url);
    groupName += groups ? groups[1] : 'icons';
    } else {
    groupName += 'icons';
    }
    //处理多倍图的情况
    if (RETINA) {
    image.retina = RETINA;
    image.ratio = 1;
    let ratio = REG_SPRITES_RETINA.exec(image.url);
    if (ratio) {
    ratio = ratio[1];
    while (ratio > 10) {
    ratio = ratio / 10;
    }
    image.ratio = ratio;
    image.groups = image.groups.filter((group) => {
    return ('@' + ratio + 'x') !== group;
    });
    groupName += '@' + ratio + 'x';
    }
    }
    return Promise.resolve(groupName);
    },
    // 转换百分比定位
    hooks: {
    onUpdateRule: function(rule, token, image) {
    var backgroundSizeX = (image.spriteWidth / image.coords.width) * 100;
    var backgroundSizeY = (image.spriteHeight / image.coords.height) * 100;
    var backgroundPositionX = (image.coords.x / (image.spriteWidth - image.coords.width)) * 100;
    var backgroundPositionY = (image.coords.y / (image.spriteHeight - image.coords.height)) * 100;

    backgroundSizeX = isNaN(backgroundSizeX) ? 0 : backgroundSizeX;
    backgroundSizeY = isNaN(backgroundSizeY) ? 0 : backgroundSizeY;
    backgroundPositionX = isNaN(backgroundPositionX) ? 0 : backgroundPositionX;
    backgroundPositionY = isNaN(backgroundPositionY) ? 0 : backgroundPositionY;

    var backgroundImage = postcss.decl({
    prop: 'background-image',
    value: 'url(' + image.spriteUrl + ')'
    });

    var backgroundSize = postcss.decl({
    prop: 'background-size',
    value: backgroundSizeX + '% ' + backgroundSizeY + '%'
    });

    var backgroundPosition = postcss.decl({
    prop: 'background-position',
    value: backgroundPositionX + '% ' + backgroundPositionY + '%'
    });

    rule.insertAfter(token, backgroundImage);
    rule.insertAfter(backgroundImage, backgroundPosition);
    rule.insertAfter(backgroundPosition, backgroundSize);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      //postcss-config.js
    module.exports = {
    plugins: [
    require('autoprefixer')({
    browsers: ['last 10 Chrome versions', 'last 5 Firefox versions', 'Safari >= 6', 'ie> 8']
    }),
    require('postcss-sprites')(
    require('./postcss-sprites.config')
    )
    ]
    }
  3. 效果

静态服务器的开启

webpack4 提供了一个devServer 的选项配合我们的服务器配置
我们只需要在配置中写入

1
2
3
4
5
6
7
8
9
10
...
devServer: {
contentBase: './dist',
host: 'localhost', // 默认是localhost
port: 3000, // 端口
open: true, // 自动打开浏览器
hot: true // 开启热更新
overlay: true, // 如果代码出错,会在浏览器页面弹出“浮动层”
} //开发服务器设置
...

需要注意的是:需要在指定文件插入这行代码才会进行热更新

1
2
3
4
if (module.hot) {
// 实现热更新
module.hot.accept();
}

跨域处理

以前我们本地开发跨域需要配置什么apache 什么转发什么的,但是在 webpack4 里面我们可以直接在配置开发服务器这里配一个跨域代理转发,这样岂不是更爽

1
2
3
4
5
6
7
8
9
10
11
12
13
devServer: {
...
proxy: {
// 跨域代理转发
'/api': {
target: 'https://xxx.xxx.com/xxx',
pathRewrite: { '^/api': '/' },
changeOrigin: true,
logLevel:"debug",
}
},
...
}

解析 resolve

webpack 提供了 resolve 选项供我们配置模块如何被解析。比如我们最常使用到的别名、省略后缀名

1
2
3
4
5
6
7
8
9
10
...
resolve: {
// 别名
alias:{
main: path.join(__dirname, 'src/main.js')
},
//省略后缀名
extensions:['.js','.json','.less','.scss','.css']
}
...

设置之后引入就可以变成这样子,简单来说就是更加快活了,不然你要陷入 ../../../../../ 的无限地狱

1
2
3
4
5
6
7
8
9
10
11
//index.js
//before
import './css/style-css.css';
import './less/style-less.less';
import './scss/style-scss.scss';
import 'main.js'
//after
import './css/style-css';
import './less/style-less';
import './scss/style-scss';
import 'main';

多页面应用的提取公共代码和单页面应用懒加载

提取公共代码

在开发多页面应用的时候,难免要提取公共代码,在 webpack4 中可以通过配置 optimization 选项中的 splitChunks 来实现公共代码的提取。

我们先来准备一些文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//utils.js
export default 'utils'

//pageA-main.js
import util from './utils'
export default 'pageA-main-' + util

//pageB-main.js
import util from './utils'
export default 'pageB-main-' + util

//入口文件 pageA
import pageA from './pageA-main'
import utils from './utils'
import * as _ from 'lodash';
console.log('pageA')
console.log(_.uniq([1,1,2,3,3]))
console.log(pageA,utils)

//入口文件 pageB
import pageA from './pageA-main'
import pageB from './pageB-main'
import * as _ from 'lodash';
console.log('pageB')
console.log(_.uniq([3,3,3,3,3]))
console.log(pageA,pageB)

先写一个多页面的入口和出口

1
2
3
4
5
6
7
8
9
10
11
...
entry:{
pageA:'./src/pageA.js',
pageB:'./src/pageB.js'
}, //单页面入口
output:{
filename: '[name].js', // 打包后文件名
path: path.resolve('dist'), // 打包后目录,需为绝对路径
chunkFilename: "[name].chunk.js"
},
...

然后我们配置 optimization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
optimization:{
splitChunks:{
cacheGroups:{
vendor:{
// 抽离第三方插件 打包node_modules下的第三方包
test: /node_modules/,
chunks:'initial',
// 打包后的文件名,任意命名
name:'vendor',
// 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包
priority:10
},
utils:{
// 抽离自己写的公共代码
chunks:'initial',
name:'utils',
minSize:0
}
}
}
},
...

这样配置完成之后其实就已经可以打包出分包的 js 文件了,当你需要输出的 html 中引用它们的时候,则需要在插件 html-webpack-plugin 中指定引用的块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
plugins:[
...
new HtmlWebpackPlugin({
template:'./src/index.html', // 打包目标模板
filename:'pageA.html',
chunks:['vendor','utils','pageA']
}),
new HtmlWebpackPlugin({
template:'./src/index.html', // 打包目标模板
filename:'pageB.html',
chunks:['vendor','utils','pageB']
}),
...
],

提取公共代码之前打包:

提取公共代码之后打包:

懒加载

webpack 中除了静态导入还有动态导入模块的方式,可以使用内置的 import(),写起来大概是这样子的。

1
2
3
4
5
import(
/* webpackChunkName: "lodash" */
`lodash`).then((_) =>{
console.log(_.uniq([1,1,2,3,3]))
})

这里需要装一个 @babel/plugin-syntax-dynamic-import 来帮助我们解析

1
2
3
4
5
// .babelrc
{
"presets": ["@babel/preset-env"], // 从右向左解析
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}

output 选项中加入 chunkFilename

1
2
3
4
5
6
7
8
// webpack.config.js
...
output:{,
path: path.resolve('dist'), // 打包后目录,需为绝对路径
filename: '[name].[hash:8].js', // 打包后文件名
chunkFilename: '[name].[hash:8].js'
},
...


这里其实最核心的一点应用其实在于 js懒加载 ,例如我找个元素,然后让它点击之后才会加载,这样子就能把整个 文件瘦身成很多个小块

1
2
3
4
5
6
7
8
9
10
window.onload = () => {
document.getElementById('webpack').onclick = e =>{
//只有该元素点击的时候才会去拉取这个块
import(
/* webpackChunkName: "lodash" */
`lodash`).then((_) =>{
console.log(_.uniq([1,1,2,3,3]))
})
}
}

懒加载不仅可以用在 js 模块上,css 文件也可以懒加载,只要你想,一切都可以懒

1
2
3
4
5
6
...
import(
/* webpackChunkName: "style" */
`./scss/style-scss.scss`).then((_) =>{
console.log('style-load')
})

生产环境跟开发环境

如果你从上面写到这里的话你会发现,整个文件越来越长,越来越大的难以忍受难以看懂,这里为了方便我们开发我们需要把 webpack.config.js 拆分成 webpack.common.js webpack.dev.jswebpack.prod.js ,然后引用个插件来 复合产生两个文件来供我们两个环境使用

1
cnpm i -D webpack-merge

这里提取文件的思路是,把公共的代码提取到 webpack.common.js 中,这里一般配置一些常用的东西,比如 entryoutputmoduleresolve 这些配置,因为这些配置开发环境跟生产环境通用的东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// webpack.common.js
const path = require('path')
//首先引入插件,插件是一个类,使用的时候需要先 new
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
//需要装 cross-env 来做跨平台的兼容
// 将package.json 中的 scripts "build": "cross-env NODE_ENV=production webpack"
// 这样代码就能知道我们是处于什么环境打包的
const devMode = process.env.NODE_ENV !== 'production' //判断是否生产环境
const styleLoader = 'style-loader'
const miniLoader = {
loader:MiniCssExtractPlugin.loader,
options:{
publicPath: '../'
}
}
// 这个插件暂时不支持 HMR ,开发环境中还是使用 style-loader
const extractLoader = devMode ? styleLoader : miniLoader
const cssLoader = 'css-loader'
const postcssLoader = 'postcss-loader'
const lessLoader = 'less-loader'
const sassLoader = 'sass-loader'
const cssLoaders = [
extractLoader,
cssLoader,
postcssLoader,
]
const lessLoaders = [
...cssLoaders,
lessLoader,
]
const sassLoaders = [
...cssLoaders,
sassLoader,
]
module.exports = {
entry:{
index:'./src/index.js'
}, //单页面入口
output:{
path: path.resolve('dist'), // 打包后目录,需为绝对路径
filename: '[name].[hash:8].js', // 打包后文件名
chunkFilename: '[name].[hash:8].js'
}, //出口
module:{
rules:[
{
test: /\.css$/, //匹配查找 css 文件
include: path.resolve(__dirname, 'src'),
use: cssLoaders
},
{
test: /\.less$/, //匹配查找 less 文件
include: path.resolve(__dirname, 'src'),
use: lessLoaders
},
{
test: /\.scss$/, //匹配查找 scss 文件
include: path.resolve(__dirname, 'src'),
use:sassLoaders
},
{
test:/\.js$/,
use: 'babel-loader',
include: /src/, // 只转化src目录下的js
exclude: /node_modules/ // 排除掉node_modules,优化打包速度
},
{
test:/\.(jpe?g|png|gif)$/,
exclude: /src\//, // 排除掉node_modules,优化打包速度
use:[
{
loader: 'url-loader',
options: {
limit: 1000, // 小于8k的图片自动转成base64格式,并且不会存在实体图片
outputPath: 'images/' // 图片打包后存放的目录
}
}
]
},
{
test: /\.(htm|html)$/,
include: path.resolve(__dirname, 'src'),
use: 'html-withimg-loader' // html里面所有的img链接
},

{
test: /\.(eot|ttf|woff|svg)$/,
include: path.resolve(__dirname, 'src'),
use: 'file-loader'
}
]
}, //处理对应模块
resolve: {
// 别名
alias:{
main: path.join(__dirname, 'src/main.js')
},
//省略后缀名
extensions:['.js','.json','.less','.scss','.css']
}
}

提取出 webpack.common.js 之后我们来接着写另外两个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// webpack.dev.js
const common = require('./webpack.common')
const merge = require('webpack-merge')
//配置开发服务器,开发工具等
module.exports = merge(common,{
mode:'development', //模式配置
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
host: 'localhost', // 默认是localhost
port: 3000, // 端口
open: true, // 自动打开浏览器
hot: true // 开启热更新
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// webpack.prod.js
//首先引入插件,插件是一个类,使用的时候需要先 new
const CleanWebpackPlugin = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const common = require('./webpack.common')
const merge = require('webpack-merge')
//配置打包文件,压缩文件,提取文件等操作
module.exports = merge(common, {
mode:'production', //模式配置
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true // set to true if you want JS source maps
}),
new OptimizeCSSAssetsPlugin({})
]
},
plugins:[
new CleanWebpackPlugin(), //清理文件夹
new HtmlWebpackPlugin({
template:'./src/index.html', // 打包目标模板
// title:'我被打包了',// 不用 template 时候生效
filename:'index.html', // 换个文件名
}),
new MiniCssExtractPlugin({
filename:'css/[name].css'
}),
], //对应插件
})

提取出来后去 package.json 中配置脚本参数

1
2
3
4
"scripts": {
"build": "cross-env NODE_ENV=production webpack --config webpack.prod.js",
"dev": "webpack-dev-server --config webpack.dev.js"
}

这样你就把所有的东西都干完啦。