以下内容都是基于 Webpack 4.x
什么是 Webpack
Webpack 可以理解为模块打包工具, 它所做的事情: 分析项目结构, 找到 JS 模块及其它一些浏览器不能直接运行的拓展语言(如 SCSS、TypeScript 等), 并将其打包为合适的格式以供浏览器使用
构建就是把源代码转换成发布到线上的可执行 JS、CSS、HTML 代码, 包括以下内容
- 代码转换: TS 编译为 JS, SCSS 编译为 CSS 等
- 文件优化: 压缩 JS、CSS、HTML 代码, 压缩合并图片等
- 代码分割: 提取多个页面的公共代码, 提取首屏不需要执行部分的代码让其异步加载等
- 模块合并: 在采用模块化的项目中会有很多个模块和文件, 需要构建功能把模块分类合并成一个文件等
- 自动刷新: 监听本地源代码的变化, 自动重新构建、刷新浏览器等
- 代码校验: 在代码被提交到仓库前需要校验代码是否符合规范, 以及单元测试是否通过等
- 自动发布: 更新完代码, 自动构建出线上发布代码并传输给发布系统
构建其实是工程化、自动化思想在前端开发中的体现, 把一系列流程用代码实现, 让代码自动化执行这一系列复杂流程; 构建给前端开发注入了更大的活力, 解放生产力 (/假的
初始化项目 & 简单配置
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
| mkdir webpack-proj && cd webpack-proj npm init -y
mkdir dist mkdir src && cd src && touch index.js
npm i webpack@4 webpack-cli@3 -D
npm i css-loader style-loader file-loader url-loader html-loader webpack-dev-server html-webpack-plugin clean-webpack-plugin -D
|
webpack 核心概念
- entry: 入口, webpack 执行构建的第一步从 entry 开始, 可抽象成输入
- module: 模块, 在 webpack 中一切皆模块, 一个模块对应着一个文件, webpack 会从配置的 entry 开始递归找出所有依赖的模块
- chunk: 代码块, 一个 chunk 由多个模块组合而成, 用于代码合并与分割
- loader: 模块转换器, 用于把模块原内容按照需求转换成新内容
- plugin: 插件, 在 webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或自定义动作
- output: 输出结果, 在 webpack 经过一系列处理并得出最终想要的代码后输出结果
webpack 启动后会从 entry 中配置的 module 开始递归解析 entry 以来的所有 module; 每找到一个 module, 就会根据配置的 loader 去找出对应的转换规则, 对 module 进行转换后, 在解析出当前 module 依赖的 module; 这些模块会以 entry 为单位进行分组, 一个 entry 和其所有依赖的 module 被分到一个组(chunk); 最后 webpack 会把所有 chunk 转换成输出文件; 在整个流程中 webpack 会在适当的时机执行 plugin 中定义的逻辑、插件
1 2 3 4 5 6 7 8 9 10 11 12
| <head> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <div id='app'></div> <script src='https://g.alicdn.xxx.com'></script> </body>
|
1 2 3 4 5 6 7 8 9
| require('./index.css'); document.getElementById('app').innerHTML = 'edsyang';
import src from './images/avatar.png'; const img = new Images(); img.src = src; document.body.appendChild(img);
|
1 2 3 4 5
| "script": { "build": "webpack --mode development", "dev": "webpack-dev-server --open --mode development" }
|
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
|
const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = { entry: './src/index.js', output: { path: path.join(__dirname, 'dist'), filename: '[name].[hash:8].js' }, module: { rules: [{ test: /\.css$/, loader: ['style-loader', 'css-loader'] }, { test: /\.(png|jpg|gif|svg|bmp)$/, loader: 'file-loader' }, { test: /\.(html|htm)$/, loader: 'html-loader' }] }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ template: './src/index.html', filename: 'index.html', title: 'edsyang\'s blog', chunks: ['vendor', 'index'], hash: true, minify: { removeAttributeQuotes: true } }) ], devServer: { contentBase: './dist', host: 'localhost', port: 8080, compress: true } }
|
接下来只需要执行 script 的命令「npm run build
」即可在 dist 文件夹中得到打包后的文件

打包结果解析
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 102 103 104 105
| (function(modules) { var installedModules = {};
function __webpack_require__(moduleId) {
if(installedModules[moduleId]) { return installedModules[moduleId].exports; } var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} };
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports; }
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
__webpack_require__.d = function(exports, name, getter) { if(!__webpack_require__.o(exports, name)) { Object.defineProperty(exports, name, { enumerable: true, get: getter }); } };
__webpack_require__.r = function(exports) { if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); } Object.defineProperty(exports, '__esModule', { value: true }); };
__webpack_require__.t = function(value, mode) { if(mode & 1) value = __webpack_require__(value); if(mode & 8) return value; if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; var ns = Object.create(null); __webpack_require__.r(ns); Object.defineProperty(ns, 'default', { enumerable: true, value: value }); if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); return ns; };
__webpack_require__.n = function(module) { var getter = module && module.__esModule ? function getDefault() { return module['default']; } : function getModuleExports() { return module; }; __webpack_require__.d(getter, 'a', getter); return getter; };
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
__webpack_require__.p = "";
return __webpack_require__(__webpack_require__.s = "./src/index.js"); })
({
"./src/index.js":
(function(module, exports) {
eval("console.log('hello');\ndocument.getElementById('app').innerHTML = 'edsyang';\n\n//# sourceURL=webpack:///./src/index.js?");
})
});
|
模块公用代码库的处理方案
- (模块中不需要再引入)使用插件: webpack.ProvidePlugin「自动向模块内部注入变量」
1 2 3 4 5
| plugin: [ new webpack.ProvidePlugin({ React: 'react', }) ]
|
- (在最外层引入)使用插件: expose-loader「expose-loader?全局变量名!模块名」会先加载该模块, 然后得到模块的导出对象, 并且挂载到 window 上
1 2 3 4 5 6 7 8 9
| const React = require('expose-loader?React!react');
module: [ rules: [{ test: /^react$/, loader: 'expose-loader?React' }] ] const React = require('react');
|
- (使用需引入, 减少本身包的体积)利用 webpack: externals 配置
1 2 3 4 5 6 7 8
| <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> // webpack 中配置 externals: { 'react': 'var window.React', 'react-dom': 'var window.ReactDOM' }
|
为什么要区分生产环境和开发环境?
开发环境(development)和生产环境(production)的构建目标差异很大; 在开发环境中, 我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server; 而在生产环境中, 我们的目标则转向于更小的 bundle, 更轻量的 source map, 以及更优化的资源, 以改善加载时间; 由于要遵循逻辑分离, 我们通常建议像每个环境编写彼此独立的 webpack 配置.