核心概念

webpack的四个核心概念:entry、output、loader、plugins

mode

mode 通过选择 development 或 production 之中的一个,来设置 mode 参数,你可以启用相应模式下的 webpack 内置的优化

1
mode: 'production'

也可以从cli参数中传递

1
2
webpack --mode=development/production
/* 环境不同,启用的插件不同 */

entry

entry 指示webpack应该使用哪个模块,来作为其构建内部依赖图的开始。进入入口起点后,webpack会找出有哪些模块和库是入口起点(直接和间接)依赖的。
entry 的配置值可以是string | object | array
可以通过在 webpack 配置中配置 entry 属性,来指定一个入口起点(或多个入口起点)。默认值为 ./src。

1
2
3
4
entry: {
path.join(__dirname, '../src/index.js')
}
/* 此处用了node的path模块,join方法用于连接多个目录,自动区分windows和linux的连接符;__dirname是node的一个全局变量,获得当前文件所在目录的完整目录名 */

output

output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。

output 的最低要求是,将它的值设置为一个对象:

  1. filename 设置输出文件名;
  2. path 设置目标输出目录的绝对路径。
    1
    2
    3
    4
    output: {
    filename: 'bundle.js',
    path: path.join(__dirname, '../dist')
    }

loader

loader 用于对模块的源代码进行转换。用于转换某些类型的模块,让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。

在配置loader时:

  1. test 用于标识出应该被对应的 loader 进行转换的某个或某些文件;
  2. use 属性,表示进行转换时,应该使用哪个 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
  module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
/* “嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.vue' 的路径」时,在你对它打包之前,先使用 vue-loader 转换一下。” */
{
test: /\.css$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
}
`

除了通过配置指定loader,还可以通过内联和cli指定(不推荐)
除了使用 package.json 常见的 main 属性,还可以将普通的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段(不明白?)
loader 通过(loader)预处理函数,为 JavaScript 生态系统提供了更多能力。 用户现在可以更加灵活地引入细粒度逻辑,例如压缩、打包、语言翻译和其他更多(O__O “…)

plugins

plugins 用于执行范围更广的任务,从打包优化和压缩,一直到重新定义环境中的变量。

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。

也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。

1
2
3
4
5
const VueLoaderPlugin = require('vue-loader/lib/plugin')

plugins: [
new VueLoaderPlugin()
]

配置

webpack 的配置文件,是导出一个对象的 JavaScript 文件。此对象,由 webpack 根据对象定义的属性进行解析。
可以用数组,导出多个配置对象。

构建目标

1
2
3
target: 'node'
// 默认值为 web
// 使用 node webpack 会编译为用于「类 Node.js」环境(使用 Node.js 的 require ,而不是使用任意内置模块(如 fs 或 path)来加载 chunk)。

项目实践

从零开始,搭建一个最简单的vue项目。虽然现在有各种脚手架为我们定制了完美的方案,但是总有一种摸不到深层的不安全感,亲自深入了解一下,可以对自己所做的事情有更多的掌控,这种感觉才是美好滴。

步骤:

  1. 创建项目文件夹

    1
    mkdir test-pack && cd test-pack
  2. 初始化,生成package.json

    1
    npm init
  3. 安装依赖项

    1
    2
    3
    4
    5
    npm install webpack webpack-cli webpack-dev-server --save-dev
    npm install clean-webpack-plugin html-webpack-plugin webpack-merge --save-dev
    npm install vue --save
    npm install vue-loader vue-template-compiler --save-dev
    npm install style-loader css-loader --save-dev

安装完成后,各依赖项如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"devDependencies": {
"clean-webpack-plugin": "^2.0.1", // build之前先清空dist目录
"css-loader": "^2.1.1",
"html-webpack-plugin": "^3.2.0", // 自动管理输出的index.html文件
"style-loader": "^0.23.1",
"vue-loader": "^15.7.0",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0",
"webpack-dev-server": "^3.3.0",
"webpack-merge": "^4.2.1" // 处理通用配置
},
"dependencies": {
"vue": "^2.6.10"
}

  1. 组织文件结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    "test-pack": {
    "node_modules"
    "dist"
    "config": {
    "webpack.base.js"
    "webpack.dev.js"
    "webpack.prod.js"
    }
    "src": {
    "components": {
    "app.vue"
    }
    "index.js"
    }
    "package.json"
    }
  2. app.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <template>
    <div class="test">{{text}}</div>
    </template>
    <script>
    export default {
    data() {
    return {
    text: '测试'
    }
    }
    }
    </script>
    <style>
    .test {
    color: red;
    }
    </style>
  3. index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import Vue from 'vue'
    import App from './components/app.vue'

    const root = document.createElement('div')
    document.body.appendChild(root)

    new Vue({
    render:(h) => h(App)
    }).$mount(root)
  4. 在webpack.base.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
    const path = require('path')
    const VueLoaderPlugin = require('vue-loader/lib/plugin')
    const HTMLPlugin = require('html-webpack-plugin')

    module.exports = {
    entry: path.join(__dirname, '../src/index.js'),
    output: {
    filename: '[name].[chunkhash].js', // hash值确保浏览器拿取最新文件
    path: path.join(__dirname, '../dist')
    },
    module: {
    rules: [
    {
    test: /\.vue$/,
    loader: 'vue-loader'
    },
    {
    test: /\.css$/,
    use: [
    'style-loader',
    'css-loader'
    ]
    }
    ]
    },
    plugins: [
    new VueLoaderPlugin(),
    new HTMLPlugin({title: '测试'})
    ]
    }
  5. 在webpack.dev.js中书写开发环境的配置,让代码在本地跑起来

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const merge = require('webpack-merge')
    const base = require('./webpack.base')
    const webpack = require('webpack')

    module.exports = merge(base, {
    mode: 'development',
    plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin()
    ],
    devServer: {
    port: 8000,
    host: '0.0.0.0',
    hot: true,
    overlay: {
    error: true
    }
    }
    })
  6. 在package.json中添加运行本地服务的脚本命令

    1
    2
    3
    "scripts": {
    "dev": "webpack-dev-server --config config/webpack.dev.js"
    }

此时,运行npm run dev,可在本地跑起来,并支持热更新

  1. 在webpack.build.js中书写生产环境的打包配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const merge = require('webpack-merge')
    const base = require('./webpack.base')
    const CleanWebpackPlugin = require('clean-webpack-plugin')

    module.exports = merge(base, {
    mode: 'production',
    plugins: [
    new CleanWebpackPlugin()
    ]
    })
  2. 在package.json中添加打包的脚本命令

    1
    2
    3
    4
    "scripts": {
    "dev": "webpack-dev-server --config config/webpack.dev.js",
    "build": "webpack --config config/webpack.prod.js"
    }

此时,运行 npm run build,可以看到文件被打包为bundle.js放在dist目录下,同时自动生成index.html,点击运行index.html可以看到网页跟本地运行一样的效果

  1. 由于添加了html-webpack-plugin插件,在dist目录下自动生成了index.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>测试</title>
    </head>
    <body>
    <script type="text/javascript" src="main.40cda2cd05192f9fc326.js"></script></body>
    </html>

打开index.html可以查看效果

至此,一个最简单的从零搭建项目的流程就走完了,后续可以添加更多东西以使项目变得更加完美。

tips

在配置时,有很多详细的参数,可以到配置查询
具体使用指南

  1. 通过安装依赖,可以使用不同的语言来书写配置文件,如typeScript/coffeeScript/jsx/babel等
  2. 除了导出单个配置对象,还可以导出一个函数、Promise、多个对象
  3. entry 简单规则:每个 HTML 页面都有一个入口起点,单页应用(SPA):一个入口起点,多页应用(MPA):多个入口起点
  4. 错误处理机制
  5. loader 总是从右到左地被调用(loader的调用很有讲究,可以抽空研究)
  6. 模块方法:ES6/CommonJS/AMD
  7. 编写一个插件
  8. package.json里面移除”main”: “index.js”,添加”private”: true,防止意外发布代码
  9. 图片等静态资源可以统一放到一个公共的地方,不过为了方便引入,可以使用alias修改路径
  10. Manifest可以对“模块映射到输出 bundle 的过程”进行追踪
  11. 通常为不同的环境编写不同的配置,可以用 webpack-merge 处理通用配置,不必重复代码
  12. 建议在生产环境中使用 uglifyjs-webpack-plugin 和 inline-source-map
  13. CommonsChunkPlugin 提取公共 bundle ,防止代码重复
  14. webpack-chart 等 bundle 分析工具
  15. 离线PWA/workBox
  16. 使用环境变量NODE_ENV
  17. webpack 可以跟其它工具集成
  18. loaders
  19. plugins

一点感悟

  1. 工作中做项目时,前辈们早已搭好环境,抑或网上早有官方出品的各种“完美”脚手架,但是只有亲自研究一番,才拥有真正属于自己一份信心与豁然;
  2. 日常工作每天都有接触,照理说随便花点时间看看资料就明白了,但是一旦开始一个新的目标,我喜欢把它吃透一点,生怕错过什么了不得的风景。事实证明,我的想法是对的。刚开始只花了几个小时就能自己搭建一个环境出来,本来满心欢喜地准备收工,但心里总不踏实,于是耐着性子把官方所有文档都看了一遍,再从网上找了视频来看。在看文档的过程中,才发现自己最先搭的环境很粗糙,后来添加了 html-webpack-plugin 来自动生成index.html文件,添加了 clean-webpack-plugin 来清理dist目录,添加了 webpack-merge 来处理重复代码……这样,项目才一步一步变成自己喜欢的样子。在看视频的时候,也是发现了些新大陆,比如,node_modules 目录下的 .bin 放的是可执行文件,直接点击里面的文件,可以实现点击应用程序图标的效果,虽然这些不是什么了不得的东西,但是,重新拾起这些被忽略的点,也是很酷的呀。