node v16.13.0
npm v8.1.4
react v17
webpack v5 - 官网
相关概念
Entry:编译入口,webpack 编译的起点Compiler:编译管理器,webpack 启动后会创建compiler对象,该对象一直存活知道结束退出Compilation:单次编辑过程的管理器,比如watch = true时,运行过程中只有一个compiler但每次文件变更触发重新编译时,都会创建一个新的compilation对象Dependence:依赖对象,webpack 基于该类型记录模块间依赖关系Module:webpack 内部所有资源都会以“module”对象形式存在,所有关于资源的操作、转译、合并都是以 “module” 为基本单位进行的Chunk:编译完成准备输出时,webpack 会将module按特定的规则组织成一个一个的chunk,这些chunk某种程度上跟最终输出一一对应Loader:资源内容转换器,其实就是实现从内容 A 转换 B 的转换器Plugin:webpack构建过程中,会在特定的时机广播对应的事件,插件监听这些事件,在特定时间点介入编译过程
安装环境
$ mkdir webpack-demo
$ cd webpack-demo
$ npm init -y
$ npm i react react-dom -S
$ npm i webpack webpack-cli -D
$ npm i webpack-merge -D // webpack-marge合并通用配置和特定环境配置
//安装完成
//"react": "^17.0.2",
//"react-dom": "^17.0.2"
//"webpack": "^5.65.0",
//"webpack-cli": "^4.9.1",
创建以下目录结构、文件和内容:
webpack-demo
|- package.json
|- package-lock.json
+ |- /src
+ |- hello.js
+ |- index.js
+ |- /config
+ |- webpack.common.js //通用环境配置文件
+ |- webpack.dev.js //开发环境配置文件
+ |- webpack.prod.js //生产环境配置文件
hello.js:
function component() {
const element = document.createElement('div');
element.innerHTML = '【hello.js】Hellp webpack';
return element;
}
document.body.appendChild(component());
index.js:
import './hello.js'
function component() {
const element = document.createElement('div');
element.innerHTML = '【index.js】哈哈';
return element;
}
document.body.appendChild(component());
webpack.common.js:
module.exports = {} // 暂不添加配置
webpack.dev.js:
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {}) // 暂不添加配置
webpack.prod.js:
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {}) // 暂不添加配置
package.json:
{
"name": "webpack-demo",
...
- "main": "index.js",
+ "private": true,
...
}
模式(mode)
修改webpack.dev.js:
module.exports = merge(common, {
mode: 'development', // 开发模式
})
修改webpack.prod.js:
module.exports = merge(common, {
mode: 'production', // 生产模式
})
entry 和 output
入口起点(entry points) 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。
修改 webpack.commom.js:
const path = require('path')
module.exports = {
entry: { // 入口
index: './src/index.js',
},
output: { //输出
filename: '[name].bundle.js', // bundle 文件名称
path: path.resolve(__dirname, '../dist'), // bundle 文件路径
clean: true // 编译前清除目录
},
}
执行:
$ npx webpack --config config/webpack.dev.js
可以在命令行中看到打包的结果,并且在根目录下生成了一个dist目录及输出文件
html-webpack-plugin
从模板生成一个HTML文件
$ npm i html-webpack-plugin -D //安装
新建 src/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
修改 webpack.commom.js:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
//...
plugins: [
// 生成html,自动引入所有bundle
new HtmlWebpackPlugin({
title: 'webpack-demo',
template: path.resolve(__dirname, '../src/index.html'),
filename: 'index.html',
}),
],
//...
}
执行:
$ npx webpack --config config/webpack.dev.js
编译后新生成了 index.html,自动插入了标题和script,如下图:
webpack-dev-server 自动编译
webpack-dev-server 提供了一个基本的 web server,并且具有实时重新加载功能,帮助我们在代码发生变化后自动编译代码。
webpack-dev-server 默认配置 conpress: true,为每个静态文件开启 gzip compression
$ npm i webpack-dev-server -D //安装
修改 webpack.dev.js:
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
const path = require('path')
module.exports = merge(common, {
mode: 'development', // 开发模式
devServer: {
static: {
directory: path.join(__dirname, '../dist'),
},
},
})
执行:
$ npx webpack serve --open --config config/webpack.dev.js
修改一下js,可以马上看到页面对应变化
cross-env 优化编译指令
通过 cross-env 配置环境变量,区分开发环境和生产环境。
$ npm i cross-env -D
修改 package.json:
{
"scripts": {
"start": "cross-env NODE_ENV=development webpack serve --open --config config/webpack.dev.js",
"dev": "cross-env NODE_ENV=development webpack --config config/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js"
},
}
现在可以运行 webpack 指令:
npm run start:本地构建开启自动编译;npm run dev:本地构建;npm run build:生产打包;
loader
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。
加载 CSS/Sass/SCSS
安装 style-loader 、css-loader 和 sass-loader ,并在 module 配置 中添加这些 loader:
$ npm i style-loader css-loader -D
$ npm i sass-loader sass -D
修改 webpack.common.js:
module.exports = {
module: {
rules: [
{
test: /.(css|scss|sass)$/,
use: [
'style-loader', // 将 JS 字符串生成为 style 节点
'css-loader', // 将 CSS 转化成 CommonJS 模块
'sass-loader', // 将 Sass 编译成 CSS
],
},
],
},
}
新建 src/style.css:
.hello {
color: red;
}
新建 src/style.scss:
$body-color: orange;
body {
color: $body-color;
}
修改 src/hello.js:
import './style.css';
function component() {
const element = document.createElement('div');
element.innerHTML = '【hello.js】Hellp webpack';
element.classList.add('hello'); //add
return element;
}
document.body.appendChild(component());
修改 src/index.js:
import './hello.js'
import './style.scss'; //add
function component() {
const element = document.createElement('div');
element.innerHTML = '【index.js】哈哈,修改一下';
return element;
}
document.body.appendChild(component());
重新编译,查看结果:
postcss-loader
postcss-loader : 使用 PostCSS 处理 CSS 的 loader
PostCSS 是一个用 JavaScript 工具和插件转换 CSS 代码的工具。
- 可以自动为 CSS 规则添加前缀;
- 将最新的 CSS 语法转换成大多数浏览器都能理解的语法;
- css-modules 解决全局命名冲突问题。
修改样式 style.scss:
$body-color: orange;
body {
color: $body-color;
user-select:none; //add
}
$ npm i postcss-loader postcss postcss-preset-env -D //安装
配置:
module.exports = {
module: {
rules: [
{
test: /.(css|scss|sass)$/,
use: [
'style-loader', // 将 JS 字符串生成为 style 节点
{
loader: "css-loader",// 将 CSS 转化成 CommonJS 模块
options: { importLoaders: 1 },
},
{
loader: 'postcss-loader', // 将 PostCSS 编译成 CSS
options: {
postcssOptions: {
plugins: [
[
'postcss-preset-env', // postcss-preset-env 包含 autoprefixer
],
],
},
},
},
'sass-loader', // 将 Sass 编译成 CSS
],
},
],
},
}
重新编译,查看结果:
分离样式文件
通过 CSS 文件的形式引入到页面
$ npm i mini-css-extract-plugin -D // 安装
修改
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[hash:8].css'
}),
],
module: {
rules: [
{
test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件
use: [
// 'style-loader', // 将 JS 字符串生成为 style 节点
MiniCssExtractPlugin.loader, // 添加 loader
{
loader: "css-loader",// 将 CSS 转化成 CommonJS 模块
options: { importLoaders: 1 },
},
{
loader: 'postcss-loader', // 将 PostCSS 编译成 CSS
options: {
postcssOptions: {
plugins: [
[
'postcss-preset-env', // postcss-preset-env 包含 autoprefixer
],
],
},
},
},
'sass-loader', // 将 Sass 编译成 CSS
],
},
],
},
}
重新编译,查看结果:
加载图像和字体(资源模块)
webpack5 新增资源模块(Asset Modules),允许使用资源文件(字体,图标等)而无需配置额外的 loader
asset/resource:将资源分割为单独的文件,并导出 url,类似之前的 file-loader 的功能.asset/inline:将资源导出为 dataUrl 的形式,类似之前的 url-loader 的小于 limit 参数时功能.asset/source:将资源导出为源码(source code). 类似的 raw-loader 功能.asset:会根据文件大小来选择使用哪种类型,当文件小于 8 KB(默认) 的时候会使用 asset/inline,否则会使用 asset/resource
修改 webpack.common.js:
module.exports = {
module: {
rules: [
{
test: /.(css|scss|sass)$/,
use: [
'style-loader', // 将 JS 字符串生成为 style 节点
'css-loader', // 将 CSS 转化成 CommonJS 模块
'sass-loader', // 将 Sass 编译成 CSS
],
},
//加载图片
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
//加载字体
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
],
},
}
添加图片 src/icon.png,修改src/index.js:
import './hello.js'
import './style.scss';
import Icon from './icon.png'
function component() {
const element = document.createElement('div');
element.innerHTML = '【index.js】哈哈,修改一下';
// 将图像添加到我们已经存在的 div 中。
const myIcon = new Image();
myIcon.src = Icon;
element.appendChild(myIcon);
return element;
}
document.body.appendChild(component());
修改 style.scss
.hello {
color: red;
background: url('./icon.png');
}
重新编译,查看结果:
Babel(JS 兼容性)
安装依赖
babel-loader:使用 Babel 加载 ES2015+ 代码并将其转换为 ES5@babel/core:Babel 编译的核心包@babel/preset-env:Babel 编译的预设,可以理解为 Babel 插件的超集@babel/preset-react:Babel 转译 jsx语法
$ npm i babel-loader @babel/core @babel/preset-env @babel/preset-react -D
修改 index.js:
import './hello.js'
import './style.scss';
import icon from './icon.png'
import React from "react";
import ReactDOM from 'react-dom';
const name = 'IT姑凉';
const element = <div>
<h1>Hello, {name}</h1>
<img src={icon} />
</div>;
ReactDOM.render(
element,
document.getElementById('root')
);
这个时候编译是报错的
修改 webpack.common.js:
module.exports = {
module: {
rules: [
//babel
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/, //不转换的文件
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-react','@babel/preset-env']
}
}
}
],
},
}
重新编译,查看结果:
devtool Source Map
我们在开发过程中免不了需要去调试代码,这时候webpack的devtool选项就十分重要了,devtool 有许多 可用选项
常用devtool配置选项:
source-map:产生一个单独的source-map文件,功能最完全,但会减慢打包速度eval-source-map:使用eval打包源文件模块,直接在源文件中写入干净完整的source-map,不影响构建速度,但影响执行速度和安全,建议开发环境中使用,生产阶段不要使用cheap-module-source-map:会产生一个不带映射到列的单独的map文件,开发者工具就只能看到行,但无法对应到具体的列(符号),对调试不便cheap-module-eval-source-map:不会产生单独的map文件,(与eval-source-map类似)但开发者工具就只能看到行,但无法对应到具体的列(符号),对调试不便
为加快生产环境打包速度,不为生产环境配置 devtool
修改 webpack.dev.js:
module.exports = merge(common, {
// 开发环境,开启 source map,编译调试
devtool: 'eval-source-map',
})