初始化项目
mkdir webpack-democd webpack-demonpm init -y
什么是webpack
webpack是模块打包器,用于分析项目结构,找到javaScript模块以及其他的一些浏览器不能直接运行的拓展语言,比如scss,typeScript,并将其打包成合适的格式给浏览器用
构建就是将源代码转换成发布到线上可执行 JavaScrip、CSS、HTML 代码
- 代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等。
- 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。
- 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
- 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
- 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。
- 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
- 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。
webpack的核心
- Entry:入口,webpack执行构建的第一步从Entry开始,可抽象成输入
- Module:模块,在webpack里一切皆是模块,一个模块对应一个文件,webpack会从配置文件Entry 开始递归找出所有依赖的模块
- Chunk,代码块,一个chunk由多个模块组成,用于代码合并分割
- Loader:模块转换器,webpack只能理解JavaScript和JSON文件,Loader让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中
- Plugin:扩展插件,在webpack构建流程中的特定时机注入扩展逻辑来改变构建结果。
- Output:属性告诉webpack在哪里输出它所创建的bundle,以及如何命名这些文件
- context:context即是项目打包的路径上下文,如果指定了context,那么entry和output都是相对于上下文路径,context必须是一个绝对路径
Webpack 启动后会从Entry里配置的Module开始递归解析 Entry 依赖的所有 Module。 每找到一个 Module, 就会根据配置的Loader去找出对应的转换规则,对 Module 进行转换后,再解析出当前 Module 依赖的 Module。 这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个 Chunk。最后 Webpack 会把所有 Chunk 转换成文件输出。 在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。
配置webpack
npm install webpack webpack-cli -D
mkdir src
mkdir dist
webpack.config.js
const path=require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports={
context:process.cwd(),
entry: './src/index.js', // 入口文件地址
output: {// 配置出口文件地址
path: path.resolve(__dirname,'dist'),
filename:'bundle.js'
},
module: {// 配置模块,主要用来配置不同文件的加载器
rules: [
{ test: /\.css$/, use: ['style-loader','css-loader']}
]
},
plugins: [// 配置插件
new HtmlWebpackPlugin({template: './src/index.html'})
],
devServer: {// 配置开发服务器
contentBase:path.resolve(_dirname,'dist'), // 配置开发服务运行时的文件根目录
host:'localhost',// 开发服务器监听的主机地址
compress:true,// 开发服务器是否启动gzip等压缩
port:8080 // 开发服务器监听的端口
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
mode模式
日常前端开发中,一般都有两套模式
- 开发模式:构建结果用于本地调试,不进行代码压缩,打印debugger信息,包含sourcemap文件
生产模式,即代码都是压缩后,运行时不打印 debug 信息,静态文件不包括 sourcemap
webpack 4.x 版本引入了 mode 的概念
- 而如果是 development mode 的话,则会开启 debug 工具,运行时打印详细的错误信息,以及更加快速的增量编译构建
- 当你指定使用 production mode 时,默认会启用各种性能优化的功能,包括构建结果优化以及 webpack 运行性能优化 | 选项 | 描述 | | —- | —- | | development | 会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin | | production | 会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin |
环境差异
| 选项 | 描述 |
|---|---|
| 开发环境 | 生成sourceMap文件 需要打印debug信息 需要live reload或者hot reload的功能 |
| 生产环境 | 可能需要分离 CSS 成单独的文件,以便多个页面共享同一个 CSS 文件 需要压缩 HTML/CSS/JS 代码 需要压缩图片 |
/*
命令行配置一:
webpack的mode默认为production
webpack serve的mode默认为development
可以在模块内通过process.env.NODE_ENV获取当前的环境变量,无法在webpack配置文件中获取此变量
*/
"scripts": {
"build": "webpack",
"start": "webpack serve"
},
// index.js
console.log(process.env.NODE_ENV);// development | production
// webpack.config.js
console.log('NODE_ENV',process.env.NODE_ENV);// undefined
/*
配置二:
同配置一,
mode:用来设置模块内的process.env.NODE_ENV
*/
"scripts": {
"build": "webpack --mode=production",
"start": "webpack --mode=development serve"
},
/*
配置三:
无法在模块内通过process.env.NODE_ENV访问
可以通过webpack 配置文件中中通过函数获取当前环境变量
env:用来设置webpack配置文件的函数参数
*/
"scripts": {
"dev": "webpack serve --env=development",
"build": "webpack --env=production",
}
// index.js
console.log(process.env.NODE_ENV);// undefined
// webpack.config.js
console.log('NODE_ENV',process.env.NODE_ENV);// development | production
/*
配置四
cross-env:用来设置node环境的process.env.NODE_ENV
*/
"scripts": {
"build": "cross-env NODE_ENV=development webpack"
}
// webpack.config.js
console.log('NODE_ENV',process.env.NODE_ENV);// development | production
/*
DefinePlugin:
设置全局变量(不是window),所有模块都能读取到该变量的值
可以在任意模块内通过 process.env.NODE_ENV 获取当前的环境变量
但无法在node环境(webpack 配置文件中)下获取当前的环境变量
*/
plugins:[
new webpack.DefinePlugin({
'process.env.NODE_ENV':JSON.stringify(process.env.NODE_ENV)
// index.js
console.log(process.env.NODE_ENV);// production
// webpack.config.js
console.log('process.env.NODE_ENV',process.env.NODE_ENV);// undefined
console.log('NODE_ENV',NODE_ENV);// error !!!
开发环境配置
开发服务器配置
npm install webpack-dev-server --save-dev
| 类别 | 配置名称 | 描述 |
|---|---|---|
| output | path | 指定输出到硬盘上的目录 |
| output | publicPath | 表示的是打包生成的index.html文件里面引用资源的前缀 |
| devServer | publicPath | 表示的是打包生成的静态文件所在的位置(若是devServer里面的publicPath没有设置,则会认为是output里面设置的publicPath的值) |
| devServer | static | 用于配置提供额外静态文件内容的目录 |
webpack.config.js
module.exports = {
devServer: {
static: path.resolve(__dirname, 'public'), // 用于配置提供额外静态文件内容的目录
port: 8080,
open: true
},
}
package.json
"scripts": {
"build": "webpack",
"start": "webpack serve"
}
支持css,less,scss
- css-loader用来翻译处理@import和url() ,开启module,css-loader中有详细介绍
style-loader可以把CSS插入DOM中,开发的时候用,上线之后会提取单独的css文件
//支持css npm i style-loader css-loader -D //支持less,scss npm i less less-loader -D npm i node-sass sass-loader -Dwebpack.config.js
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode: 'development', devtool:false, entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' }, module: { rules: [// 从右往左,最右侧的接收一个源文件,最左边的loader返回一个js脚本 //为什么不用一个loader干所有事情,而是把若干个小loader串联起来: loader是单一原则,每个loader只做单一的事件 { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.less$/, use: ['style-loader','css-loader', 'less-loader'] }, { test: /\.scss$/, use: ['style-loader','css-loader', 'sass-loader'] } , { test: /\.scss$/, use: ['style-loader','css-loader','less-loader', 'sass-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({template: './src/index.html'}) ] };css兼容
为了浏览器的兼容性,有时候我们必须加入-webkit,-ms,-o,-moz这些前缀
- Trident内核:主要代表为IE浏览器, 前缀为-ms
- Gecko内核:主要代表为Firefox, 前缀为-moz
- Presto内核:主要代表为Opera, 前缀为-o
- Webkit内核:产要代表为Chrome和Safari, 前缀为-webkit
- 伪元素::placeholder可以选择一个表单元素的占位文本,它允许开发者和设计师自定义占位文本的样式。
- https://caniuse.com/
- postcss-loader可以使用PostCSS处理CSS
- postcss-preset-env把现代的CSS转换成大多数浏览器能理解的
PostCSS Preset Env已经包含了autoprefixer和browsers选项
npm i postcss-loader postcss-preset-env -Dpostcss.config.js
let postcssPresetEnv = require('postcss-preset-env'); module.exports={ plugins:[postcssPresetEnv({ browsers: 'last 5 version' })] }webpack.config.js
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode: 'development', devtool: false, entry: './src/index.js', /* entry:{ 默认来说,一个入口回生成一个代码块,这里会生成两个代码块;但是对于有些情况,代码块可以进行合并和分割 main:'./src/index.js', vendors:['react','react-dom'] // 将react,react-dom等第三方包大包成一个代码块 } */ output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader','postcss-loader'] }, { test: /\.less$/, use: ['style-loader','css-loader','postcss-loader','less-loader'] }, { test: /\.scss$/, use: ['style-loader','css-loader','postcss-loader','sass-loader'] } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ] };支持图片
file-loader: 解决css等文件中引入的图片路径问题
- url-loader:当图片小于limit的时候会把图片BASE64编码,大于limit参数的时候还是使用file-loader 进行拷贝
-
npm i file-loader url-loader -Dconst path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode: 'development', devtool: false, entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader','postcss-loader'] }, { test: /\.less$/, use: ['style-loader','css-loader','postcss-loader','less-loader'] }, { test: /\.scss$/, use: ['style-loader','css-loader','postcss-loader','sass-loader'] }, { test: /\.(jpg|png|bmp|gif|svg)$/, use:[ loader:'url-loader', options:{ esModule:false,// 是否转成es6模块 name:`[hash:8].[ext]`, limit:8*1024 // 如果文件太小,比如雪碧图,不需要拷贝文件,也不需要http请求,只需要把文件编程base64字符串内嵌到html文件中就可以了 } ] } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ] };js兼容性处理
有些代码,webpack是不认识的,比如react jsx或者有些代码浏览器不兼容的,比如es6,es7,需要靠babel-loader,
但是babel-loader只是一个转换函数,并不识别js代码,也不知道如何转换;所以得先认识js代码,知道把老代码转换成新代码,这个需要靠@babel/core,
- @babel/core它是babel的核心包,能够识别js代码,但是不知道如何转换写法
babel插件知道如何把老的语法转换成新的语法,每个插件会对应一个语法,比如箭头函数
Babel其实是一个编译JavaScript的平台,可以把ES6/ES7,React的JSX转义为ES5
- babel-loader使用Babel和webpack转译JavaScript文件
- @babel/@babel/coreBabel编译的核心包
- babel-preset-env babal预设,插件的集合
- @babel/@babel/preset-reactReact插件的Babel预设
- @babel/plugin-proposal-decorators把类和对象装饰器编译成ES5
@babel/plugin-proposal-class-properties转换静态类属性以及使用属性初始值化语法声明的属性
cnpm i babel-loader @babel/core @babel/preset-env @babel/preset-react -D cnpm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties @babel/plugin-proposal-private-property-in-object @babel/plugin-proposal-private-methods -Dconst path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { mode: 'development', devtool: false, entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'main.js', }, module: { rules: [ { test: /\.jsx?$/, use: { loader: 'babel-loader', options: { presets: ["@babel/preset-env", '@babel/preset-react'], plugins: [ ["@babel/plugin-proposal-decorators", { legacy: true }], ["@babel/plugin-proposal-private-property-in-object", { "loose": true }], ["@babel/plugin-proposal-private-methods", { "loose": true }], ["@babel/plugin-proposal-class-properties", { loose: true }], ], }, }, }, { test: /\.css$/, use: ['style-loader', 'css-loader', 'postcss-loader'] }, { test: /\.less$/, use: ['style-loader', 'css-loader','postcss-loader', 'less-loader'] }, { test: /\.scss$/, use: ['style-loader', 'css-loader', 'postcss-loader','sass-loader'] }, { test: /\.(jpg|png|gif|bmp|svg)$/, type:'asset/resource', generator:{ filename:'images/[hash][ext]' } } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ] };eslint
- eslint-loader
- configuring
- babel-eslint
- Rules
- ESlint 语法检测配置说明
```javascript const path = require(‘path’); const HtmlWebpackPlugin = require(‘html-webpack-plugin’); const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’); module.exports = { mode: ‘development’, devtool: false, entry: ‘./src/index.js’, output: { path: path.resolve(__dirname, ‘dist’), filename: ‘[name].js’, }, module: { rules: [npm install eslint eslint-loader babel-eslint --D
- {
- test: /.jsx?$/,
- loader: ‘eslint-loader’,
- enforce: ‘pre’, // loader是有分类的,pre前置,post后置,normal正常,inline内敛 对于一个js文件,可能会有多个loader进行处理;loader的执行顺序是规定好的,当某个文件想先执行,可以加enforece属性
- options: { fix: true }, // 尝试修复
- exclude: /node_modules/,
- },
{
test: /.jsx?$/,
use: {
loader: ‘babel-loader’,
options: {
} }, include: path.join(__dirname, ‘src’), exclude: /node_modules/ }, { test: /.css$/, use: [‘style-loader’, ‘css-loader’, ‘postcss-loader’] }, { test: /.less$/, use: [‘style-loader’, ‘css-loader’,’postcss-loader’, ‘less-loader’] }, { test: /.scss$/, use: [‘style-loader’, ‘css-loader’, ‘postcss-loader’,’sass-loader’] }, { test: /.(jpg|png|bmp|gif|svg)$/, use: [{ loader: ‘url-loader’, options: {"presets": ["@babel/preset-env"], "plugins": [ ["@babel/plugin-proposal-decorators", { legacy: true }], ["@babel/plugin-proposal-private-property-in-object", { "loose": true }], ["@babel/plugin-proposal-private-methods", { "loose": true }], ["@babel/plugin-proposal-class-properties", { loose: true }], ]
} }] } ] }, plugins: [ new HtmlWebpackPlugin({ template: ‘./src/index.html’ }) ] };limit: 10.eslintrc.js配置文件 ```javascript module.exports = { root: true, parser:"babel-eslint", // 将源代码转成ast语法数 //指定解析器选项 parserOptions: { sourceType: "module",// 源代码类型 ecmaVersion: 2015 }, //指定脚本的运行环境 env: { browser: true, // 浏览器环境 }, // 启用的规则及其各自的错误级别 // 先进行代码检查,如果发现不正确,回尝试修复,如果修复成功,接着执行 rules: { "indent": "off",//缩进风格 "quotes": "off",//引号类型 "no-console": "error",//禁止使用console } }airbnb规范
-
npm i eslint-config-airbnb eslint-loader eslint eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks and eslint-plugin-jsx-a11y -D.eslintrc.js
module.exports = { //"root":true 用了规范之后不再是一个跟配置文件了 "parser":"babel-eslint", "extends":"airbnb", // 继承自airbnb提供的规范 "rules":{ "semi":"error", "no-console":"off", "linebreak-style":"off", "eol-last":"off" //"indent":["error",2] }, "env":{ "browser":true, "node":true } }自动修复
安装vscode的eslint插件
- 配置自动修复参数
.vscode/setting.json
{
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"editor.codeActionsOnSave": {// 保存的时候执行的命令
"source.fixAll.eslint": true
}
}
生产环境
提取css
因为css下载和js可以并行,当一个html文件很大的时候,我们可以把css单独取出来加载
mini-css-extract-plugin
npm install --save-dev mini-css-extract-plugin
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: 'development',
devtool: false,
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
+ publicPath: '/'
},
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' },
+ { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
+ { test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] },
+ { test: /\.scss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] },
{
test: /\.(jpg|png|gif|bmp|svg)$/,
type:'asset/resource',
generator:{
filename:'images/[hash][ext]'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
+ new MiniCssExtractPlugin({
+ filename: '[name].css'// 例如main.css:css文件合并到main.css里面
+ })
]
};
压缩js,css,html
- optimize-css-assets-webpack-plugin是一个优化和压缩CSS资源的插件
- terser-webpack-plugin是一个优化和压缩JS资源的插件
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
+const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
+ mode: 'none',
devtool: false,
entry: './src/index.js',
+ optimization: {
+ minimize: true,
+ minimizer: [
+ new TerserPlugin(),
+ ],
+ },
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
publicPath: '/',
},
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
compress: true,
port: 8080,
open: true,
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'eslint-loader',
enforce: 'pre',
options: { fix: true },
exclude: /node_modules/,
},
{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: [[
'@babel/preset-env',
{
useBuiltIns: 'usage'
corejs: {
version: 3
},
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17',
},
},
], '@babel/preset-react'],
plugins: [
['@babel/plugin-proposal-decorators', { legacy: true }],
['@babel/plugin-proposal-class-properties', { loose: true }],
],
},
},
include: path.join(__dirname, 'src'),
exclude: /node_modules/,
},
{ test: /\.txt$/, use: 'raw-loader' },
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] },
{ test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'] },
{ test: /\.scss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'] },
{
test: /\.(jpg|png|gif|bmp|svg)$/,
type:'asset/resource', // webpack5可以替代以前的file-loader
generator:{
filename:'images/[hash][ext]'
}
},
{
test: /\.html$/,
loader: 'html-loader',
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
+ minify: {
+ collapseWhitespace: true,
+ removeComments: true
}
}),
new MiniCssExtractPlugin({
filename: 'css/[name].css',
}),
+ new OptimizeCssAssetsWebpackPlugin(),
],
};
