概览
webpack是模块化方案的一个工具手段。可以把浏览器不认识的文件代码翻译成认识的3大件!webpack把一切静态资源都视为模块,所以又叫静态资源打包器。
如果把webpack视为一个函数,那么webpack.config里的配置项,就可以视为这个函数的一系列参数,我们通过修改参数来控制输出结果!
weback 也是微内核的架构,核心自带的功能轻量化、很多功能都是通过一些列loader 、plugin进行扩展。
机制
通过入口文件,按照引入的依赖关系递归构建好依赖图谱,在这个过程中,遇到不同的文件类型,调用你配置好的loader进行翻译处理,最后输出浏览器可执行的代码文件。
HRM
"Hot Module Replacement" 热更新。
在开发环境中,为了提升开发效率和体验,我们希望修改源码实时且无刷新地反馈在浏览器中,这种技术就叫做HRM。
loader
- webpack 本身只能处理js模块,要处理其他文件类型,就需要不同的loader。
- 不需要单独引入,但是依赖里也是要安装的,是自动加载的
- 针对特定格式文件进行处理,test 匹配后用use 什么loader
- 类似gulp的管道机制,也就是接受源码,处理后要返回源码,供给下一个loader去处理,是链式的过程。
plugin
- 需要引入、实例化new,可以实例化多个。
- 是打包加工源码过程中某个钩子中触发执行的。webpack 提供了几十个钩子,够你植入插件逻辑。是不区分文件类型的,只是不同钩子时期的任务。
从0->1配置
现在我们不依赖任何脚手架,自己从0一点点搭建一个模块化形式的项目。本文将搭建react类型项目。
安装webpack
创建文件夹如 webpack,并在终端内执行
$ npm init 一路回车初始化项目
$ npm i webpack webpack-cli -D
默认安装的是最新版本。目前是:
webpack ^5.58.1
webpack-cli ^4.9.0
创建项目结构
webpack/
src/
index.js
index.html
package.json
各文件内容
1.index.js
function test(content) {
document.querySelector('#app').innerHTML = content;
}
test('something');
2.index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>webpack</title>
</head>
<body>
<div id="app"></div>
</body>
<script type="text/javascript" src="./output/main.js"></script>
</html>
output/main.js是打包后的js代码路径。
3.package.json
{
"name": "webpack-study",
"version": "1.0.0",
"description": "",
"main": "src/index.js",
"scripts": {
"test": "..."
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^5.58.1",
"webpack-cli": "^4.9.0"
}
}
补充打包命令,即对 ./src/index.js 文件进行编译,输出目录为output,指定开发环境参数和devtool类型可以不对产物进行压缩,便于后续的产物分析。
"scripts": {
"build": "webpack ./src/index.js -o ./output --mode=development --devtool=cheap-module-source-map"
}
执行 npm run build 命令后,输出到output/main.js。 这时候浏览器访问index.html 就可以看到something了!
从 1->2 配置
在刚才从0->1的基础上,进一步优化升级我们的配置。
一、新建webpack.config.js,将build命令执行的参数转移到该文件:
1.webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
devtool: 'cheap-module-source-map',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'output'),
filename: 'main.js'
},
}
2.修改package.json:
"scripts": {
"build": "webpack"
}
执行命令 yarn build,效果一样。
二、集成各种能力
1.增加ES6的转换能力
1.1 创建 src/es6.js文件
里面是一堆es6的语法,我们要用babel 对他们进行翻译。
1.2 安装:
$ npm i @babel/core @babel/preset-env babel-loader -D
1.3 配置
webpack.config.js
module: {
rules: [{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
],
}
}
}]
}
1.4 再次打包
查看output/main.js,发现es6语法得到转换了。
2.引入react相关配置
2.1 安装:
npm i react react-dom @babel/preset-react -S
2.2 创建:
src/react.js
2.3 写入:
import React from 'react';
import { render } from 'react-dom';
const App = () => <div>App</div>;
render(<App />, document.querySelector('#app'));
2.4 配置: webpack.config.js
entry: './src/react.js', // 修改文件入口
// module.rules[0].use.options.presets
presets: [
...
'@babel/preset-react'
],
2.5 打包: 打包查看HTML文件
3.缓存包提取
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
filename: 'vendor.js',
chunks: 'all',
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/
},
}
}
}
然后记得把产物引入 html文件。
4.css文件处理
4.1 安装:
npm i mini-css-extract-plugin -D
npm i style-loader css-loader -D
4.2 配置:
webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
...
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
})
],
...
// module.rules
// MiniCssExtractPlugin.loader 代替 style-loader
4.3 引入:
自动引入脚本和样式表
虽然上面的样式抽离成功了,但是并没有出现在 HTML文件中,我们依然要像之前的 js 文件一样手动引入。对于动态生成的文件,这显然是不现实的。于是:有了5中优化。
5.html模板
从4中暴露的问题:动态生成的css、js的文件产物,我们要手动在html中去引入,显然不够智能。所以我们希望,可以自动插入。
5.1 安装:
$ npm html-webpack-plugin -D
5.2 配置:
const HtmlWebpackPlugin = require('html-webpack-plugin')
...
plugins:[
new HtmlWebpackPlugin()
],
效果是打包文件多了一个index.html
5.3 模板:
问题是缺少 div#app 元素,所以我们使用模板文件:
plugins:[
new HtmlWebpackPlugin({
template: './template.html'
})
]
6.HMR
到目前为止,我们一直在使用打包+刷新的模式查看代码效果,显然十分繁琐低效。借助本地开发服务器来解决这个问题:
6.1 安装:
$ npm i webpack-cli webpack-dev-server -D
6.2 增加命令:
"scripts": {
"start": "webpack serve"
}
6.3 启动看效果:
我们修改App组件,可以看到页面已经实时更新了,只不过是【刷新】!继续:
webpack.config.js:
{
devServer: {
port: 8000, // 顺便更改一下端口
hot: true
}
}
入口文件:
...
render(<App />, document.querySelector('#app'));
if (module.hot) {
module.hot.accept(App, () => {
render(<App />, document.querySelector('#app'));
});
}
// 如果 App 组件是外部文件创建的,通常写作(与import导入的路径一致):
if (module.hot) {
module.hot.accept('./App', () => {
render(<App />, document.querySelector('#app'));
});
}
至此,一个功能相对完善的脚手架就搭建完成了!