webpack 基本简介
webpack 按官网文档的说法:是一个 JavaScript 应用程序的 静态模块打包器(module bundler),针对一个入口文件,webpack 会递归地构建一个依赖关系图,最终将这些依赖的模块打包成一个或多个 bundler,开发者需要做的就是在配置文件中 写好配置,然后剩下的工作 webpack 会帮开发者自动处理
webpack 的诞生历史,很有意思,听一听下面这个现实故事就了解到 webpack 是怎么诞生的~
Tobias Koppers 是 Webpack 仓库 创建者,简单讲就是 webpack 是 Tobias 的儿子,他是一个德国人,也是一个 java 开发者,却从没有写过 web 页面,有意思的是正是一个没有写过 web 页面的人却发明了当代 web 开发的基石
在 java 里面有个很出名的技术 叫 GWT(Google Web Toolkit), GWT 是把 java 代码转换成 javascript ,也就是让后端来写前端,本质上也是在 AST 层面对代码做一成转换,babel 也是干这件事的,但是 GWT 这门技术没有流行起来,后面 Google 也不推广了
说白了,java 一直想染指 web 页面的,以前 java 有 Applet,主要处理页面,动画,后来 Flash 打败了 Applet,接着 javascript又取代了FLash,但人类欲望是无止境的,现在搞 web vr,大型设计软件,javascript 的性能是满足不了的,所有出现了 WebAssembly
Tobias 在使用 GWT 里面有个 feature「code splitting」,就是他给 node.js 库 modules-webmake 提了一个 issue,希望在 node.js 内实现 feature「code splitting」,但 modules-webmake 一直没有实现这个功能。于是,在 2012 年 3 月 10 号,Tobias 在 github 上开了一个新的项目 webpack,其一开始的功能主要是 Bundle 代码
webpack 核心概念
- 入口(entry):指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始
- 输出(output):告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist
- 转换(loader):让 webpack 能够去处理那些非 JavaScript 文件(样式、图片等)
- 插件(plugins):用于执行范围更广的任务
- 模式(mode):使用相应模式的配置:development 本地模式、production 线上模式(压缩)
【操作步骤】
> 在任意盘下创建一个文件夹,并命名为 webp-demo,执行 cmd 后执行如下指令 "F:\wwwroot\webp-demo>"
> npm init -y # 用于初始一个项目 package.json 配置文件
> npm install webpack@4.x webpack-cli@3.x --save-dev # 安装 webpack 相关依赖
> md src build src\js src\css src\imgs
> 在 src/js/index.js、src/css/index.css、src/index.html 相应的路径创建相关文件
> 在根目录下创建 webpack.config.js 文件
【代码编辑】
# src/index.html 文件编码如下,引入了打包后的 JS 文件 dist/js/index.js
# 设置 webpack.config.js 配置文件的入口和输出口,配置 package.json 项目配置
> npm run build
> webp-demo@1.0.0 build F:\wwwroot\webp-demo
> webpack
asset js/build.js 1.2 KiB [emitted] (name: main)
./src/js/index.js 1 bytes [built] [code generated]
webpack 5.24.4 compiled successfully in 65 ms
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>首页</title>
<script src="../dist/js/index.js"></script>
</head>
<body>
</body>
</html>
【配置信息】
const { resolve } = require('path'); // 引入 path 处理打包文件相对路径
module.exports = {
entry: './src/js/index.js', // 打包前的 JS 文件
output: {
filename: 'js/index.js', // 打包后的 JS 文件
path: resolve(__dirname, 'dist') // 打包后的根目录
},
module: {
rules: []
},
plugins: [],
mode: 'development'
}
{
......
"scripts": {
"build": "webpack --progress --colors",
"test": "echo \"Error: no test specified\" && exit 1"
},
......
}
【编译结果】
打包后的目录结构如下,这个时候通过浏览器打开 src/index.html 文件发现什么都没有 …
|-- dist # 打包之后的目录
|-- js
|-- index.js
|-- node_modules
|-- src # 打包之前的目录
|-- css
|-- imgs
|-- js
|-- index.js
|-- index.html
|-- package-lock.json
|-- package.json
|-- webpack.config.js
给 src/js/index.js 编辑如下,然后执行 npm run build 后再刷新 src/index.html ,看到 H1 号字体的 “hello webpack” 内容
document.write('<h1>hello webpack</h1>');
webpack 页面配置
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>首页</title>
<script src="../dist/js/index.js"></script>
</head>
<body>
</body>
</html>
观察 src/index.html 代码可以发现,里面引入了打包后的 JS 脚本文件,那么能不能通过配置自动引用关联 JS 脚本文件呢?
# 安装 html-webpack-plugin 插件用于自动化引入 JS 脚本,配置如下
> npm install --save-dev html-webpack-plugin@4.x
# 将 src/index.html 内部 JS 引入去除,引入 html-webpack-plugin 插件,调整 webpack-config.js 配置文件
# 执行 npm run build 打包指令后的 dist/index.html 发现自动引入了相关 JS 脚本文件,运行 dist/index.html 效果同样
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>首页</title>
</head>
<body>
</body>
</html>
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
......
plugins: [
new HtmlWebpackPlugin({ template: resolve(__dirname, 'src/index.html'), filename: 'index.html' })
],
mode: 'development'
}
webpack 模板引擎
随着前端的发展至今,讲究的是前端的模块化、组件化,这样的开发方式易于后期的项目维护,减少后期维护成本
webpack处理组件中的模板文件有两种方式:
如果需要在模板中引用变量或遍历比较复杂的数组时,使用 JS 模板引擎(例:EJS),而非 HTML 模板
# 在前后端分离的开发模式下,前端必定在模板中涉及到较为复杂的数据渲染,因此选择 ejs-loader 处理 EJS 语法糖文件
# 安装 ejs-loader 插件用于处理 EJS 语法,配置如下
> npm install ejs-loader --save-dev
# 引入 ejs-loader 插件,调整 webpack-config.js 配置文件
# 将 src/index.html 修改为 index.ejs,再执行 npm run build 后,生成了 dist/index.html,效果相同
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
......
module: {
rules: [
{ test: /\.ejs/, loader: 'ejs-loader', options: { esModule: false } }
]
},
plugins: [
new HtmlWebpackPlugin({ template: resolve(__dirname, 'src/index.ejs'), filename: 'index.html' }),
],
mode: 'development'
}
在 src 下创建 about.ejs 文件:创建一个关于我们新的页面,代码如下,并调整配置为一个多页面项目开发
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>关于我们</title>
</head>
<body>
</body>
</html>
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
......
module: {
rules: [
{ test: /\.ejs/, loader: 'ejs-loader', options: { esModule: false } },
]
},
plugins: [
new HtmlWebpackPlugin({ template: resolve(__dirname, 'src/index.ejs'), filename: 'index.html' }),
],
mode: 'development'
}
- 执行 npm run build 打包,并生成了对应的 dist/about.html 文件,编辑器打开 dist/about.html 发现同样使用了 dist/js/index.js
- 很明显 dist/js/index.js 映射的是 dist/index.html,就需要 dist/js/about.js 去映射 dist/about.html
- 在 src/js 下创建 about.js 并调整配置文件入口和出口配置,并约束 HTML 一一映射一个或多个相应 JS 脚本文件
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: { // 参考 webpack 文档,多入口配置
index: './src/js/index.js',
about: './src/js/about.js',
},
output: {
filename: 'js/[name].js',
path: resolve(__dirname, 'dist')
},
module: {
rules: [
{ test: /\.ejs/, loader: 'ejs-loader', options: { esModule: false } },
]
},
plugins: [
new HtmlWebpackPlugin({ template: resolve(__dirname, 'src/index.ejs'), filename: 'index.html' }),
new HtmlWebpackPlugin({ template: resolve(__dirname, 'src/about.ejs'), filename: 'about.html' }),
],
mode: 'development'
}
webpack 开发的主旨不只是组件化开发,而且还能够实现工程自动化,若一个项目不只两个页面或着出现几十个界面,如果一个一个的去配置是不理想的。
这里做个项目文件调整,webpack.config.js 是一个配置文件,将每个页面看成是一个路由,在根目录下创建 route.config.js、entry.config.js、plugin.config.js、rule.config.js,通过遍历处理路由 route.config.js 解析得到 entry.config.js、plugin.config.js 配置,并引进 entry.config.js、plugin.config.js、rule.config.js 修改 webpack.config.js 文件
# route.config.js 设计如下,方便后期添加路由即可
------------------------------------------------------------------------------------------------------------
module.exports = [
{ path: 'index', en: 'HOME', title: '首页' },
{ path: 'about', en: 'ABOUT US', title: '关于我们' },
];
------------------------------------------------------------------------------------------------------------
# entry.config.js 设计如下,根据路由配置设置多入口配置
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const routes = require('./route.config');
let entry = {};
routes.map(item => {
entry[item.path] = resolve(__dirname, './src', `js/${item.path}.js`);
});
module.exports = entry;
------------------------------------------------------------------------------------------------------------
# plugin.config.js 设计如下,根据路由配置设置多界面配置
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const routes = require('./route.config');
const HtmlWebpackPlugin = require('html-webpack-plugin');
let plugins = [];
routes.map(item => {
plugins.push(
new HtmlWebpackPlugin({
template: resolve(__dirname, './src', `${item.path}.ejs`),
filename: `${item.path}.html`,
chunks: [item.path], inject: true, path:item.path, minify: true, // minify 压缩和清空注释
favicon: resolve(__dirname, './src', 'favicon.ico'),
title: item.title
})
);
});
module.exports = plugins;
------------------------------------------------------------------------------------------------------------
# rule.config.js 设计如下,根据 loader 预处理器设置多界面配置
------------------------------------------------------------------------------------------------------------
module.exports = [
{ test: /\.ejs/, loader: 'ejs-loader', options: { esModule: false } },
];
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const entry = require('./entry.config');
const plugins = require('./plugin.config');
const rules = require('./rule.config');
module.exports = {
entry,
output: {
filename: 'js/[name].js',
path: resolve(__dirname, './dist/')
},
module: {
rules: [ ...rules ],
},
plugins: [ ...plugins ],
mode: 'development'
}
这样的写法,功能分类,有助于后期项目运维,具有针对性修改对应文件,甚至连 rules 也可以抽离出来配置,后续讲解~
以上配置仅仅适用于所有的界面都只有一级的情况,如果存在多级目录的话就需要调整,例:在 src 下创建 src/about/index.ejs 关于我们详情页面,调整配置
# route.config.js 设计如下,方便后期添加路由即可
------------------------------------------------------------------------------------------------------------
module.exports = [
{ path: 'index', en: 'HOME', title: '首页' },
{ path: 'about', en: 'ABOUT US', title: '关于我们' },
{ path: 'about/index', en: 'ABOUT US', title: '关于我们详情' },
];
------------------------------------------------------------------------------------------------------------
# entry.config.js 设计如下,根据路由配置设置多入口配置
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const routes = require('./route.config');
let entry = {};
routes.map(item => {
const index = item.path.indexOf('/') > -1 ? item.path.replace(/\//g, '.') : item.path;
entry[index] = resolve(__dirname, './src', `js/${item.path}.js`);
});
module.exports = entry;
------------------------------------------------------------------------------------------------------------
# plugin.config.js 设计如下,根据路由配置设置多界面配置
------------------------------------------------------------------------------------------------------------
......
let plugins = [];
routes.map(item => {
const chunk = item.path.indexOf('/') > -1 ? item.path.replace(/\//g, '.') : item.path;
plugins.push(
new HtmlWebpackPlugin({
template: resolve(__dirname, './src', `${item.path}.ejs`),
filename: `${item.path}.html`,
chunks: [chunk], inject: true, path:item.path, minify: true, // minify 压缩和清空注释
favicon: resolve(__dirname, './src', 'favicon.ico'),
title: item.title
})
);
});
module.exports = plugins;
------------------------------------------------------------------------------------------------------------
# 执行 npm run build 打包的目录结构如下:
|-- src |-- dist
|-- about |-- about
|-- index.ejs |-- index.html
|-- js |-- js
|-- index.js |-- index.js
|-- about.js |-- about.js
|-- about/index.js |-- about.index.js
|-- favicon.ico |-- favicon.ico
|-- index.ejs |-- index.html
|-- about.ejs |-- about.html
------------------------------------------------------------------------------------------------------------
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>关于我们详情</title>
<link rel="icon" href="../favicon.ico"><!-- 会自动匹配相对路径 -->
</head>
<body>
<script src="../js/about.index.js"></script>
</body>
</html>
这里需要约束一下的仅仅是配置路由与映射 JS 脚本文件的命名,例:path: ‘about/index’ 映射的 JS 文件为 about/index.js 不能多不能少即可
观察 src/index.ejs 和 src/about.ejs 以及 src/about/index.ejs 发现,三者内的 head 标记内容基本差不多,唯一不同的是 title 标题内容,结合组件化开发,可以将相同部位代码抽离出来,在 src 下创建 components 文件夹,并创建 html-head.ejs 表示抽离的页面 head 标记内容,由于标题不同,这里引入 EJS 语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="<%= htmlWebpackPlugin.options.favicon %>" rel="shortcut icon" type="image/x-icon">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root">
<!-- 同理抽出底部代码作为 html-foot.ejs 组件内容 -->
</div>
</body>
</html>
看 EJS 语法可以得到 title 的值是通过 htmlWebpackPlugin 对象传递的,那么就需要在 htmlWebpackPlugin 对象中声明,在引用组件时,需要注意多级目录问题
# src/index.ejs 修改如下,同理抽离 html-foot.ejs
------------------------------------------------------------------------------------------------------------
<%= require('./components/html-head.ejs')({htmlWebpackPlugin}) %>
hello webpack
<%= require('./components/html-foot.ejs')({htmlWebpackPlugin}) %>
------------------------------------------------------------------------------------------------------------
# src/about.ejs 修改如下,同理抽离 html-foot.ejs
------------------------------------------------------------------------------------------------------------
<%= require('./components/html-head.ejs')({htmlWebpackPlugin}) %>
hello webpack
<%= require('./components/html-foot.ejs')({htmlWebpackPlugin}) %>
------------------------------------------------------------------------------------------------------------
# src/about/index.ejs 修改如下,同理抽离 html-foot.ejs
------------------------------------------------------------------------------------------------------------
<%= require('../components/html-head.ejs')({htmlWebpackPlugin}) %>
hello webpack
<%= require('../components/html-foot.ejs')({htmlWebpackPlugin}) %>
【EJS 语法】
<% --- %>
‘脚本’ 标签,用于流程控制,无输出
<% if (flag) { %>
<h2>为真的情况</h2>
<% } else { %>
<h2>为假的情况</h2>
<% } %>
<%= (_var) %>
输出数据到模板(输出是转义 HTML 标签)
# var message = '标题的内容';
<h2><%= message %></h2>
说白了就是 EJS 语法引用的 JS 语法,其他都使用 HTML 语法,了解掌握以上两种 EJS 语法即可
webpack 样式处理
网页结构 html、表现 css、行为 JS 三大要素才能形成一个完整的网页,学过 css 的都知道,有 3 种 样式引入:行内、内部、外联
# 在 src/css 目录下创建样式文件 index.css,内容如下,通过在 src/js/index.js 中引入样式
------------------------------------------------------------------------------------------------------------
#root { color: #f00; }
------------------------------------------------------------------------------------------------------------
# 通过 @import './public.css'; 方式引入其他样式
import '../css/index.css';
------------------------------------------------------------------------------------------------------------
# 安装 css-loader 和 mini-css-extract-plugin,需要配置 rule.config.js 和 plugin.config.js 配置
> npm install css-loader mini-css-extract-plugin --save-dev
------------------------------------------------------------------------------------------------------------
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = [
......
{ test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader' ] },
];
------------------------------------------------------------------------------------------------------------
......
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
......
plugins.push( new MiniCssExtractPlugin({ filename: 'css/[name].css' }) );
module.exports = plugins;
写页面样式 CSS 一向是一种体力活,非常的枯燥而写死,不能动态改变,需要利用 LESS、SASS 等 CSS 扩展语言,两者并没有什么区别
将 src/css/index.css 修改为 src/css/index.less,并将 src/js/index.js 引入样式 import ‘…/css/index.css’; 改成 import ‘…/css/index.lessss’;
# 安装 less 和 less-loader 预处理器来处理 less 文件转换为 css 文件,引入 less-loader 到 rule.config.js 文件即可
> npm install less less-loader --save-dev
------------------------------------------------------------------------------------------------------------
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 指定抽出样式内部引用外部资源公共路径,由于打包后 dist/css 样式引用 dist/imgs 图片,publicPath = ’../‘ 相对于父级定位
const extract = { loader: MiniCssExtractPlugin.loader, options: { publicPath: '../' } };
module.exports = [
......
{ test: /\.(c|le)ss$/, use: [ extract, 'css-loader', 'less-loader' ] },
];
------------------------------------------------------------------------------------------------------------
# 执行 npm run build 打包,得出的效果没有变化,同样在 src/css/index.less 内部引入 @import './public.less'; 并修改 public.less 样式
# 在 public.less 内部添加 #root { font-size: 16px; } 执行 npm run build 打包,得到的打包样式如下:
------------------------------------------------------------------------------------------------------------
#root { font-size: 16px; }
#root { color: #f00; }
------------------------------------------------------------------------------------------------------------
# 明明同一个样式选择器,这样的写法是不友好的,通过 optimize-css-assets-webpack-plugin 插件去处理重复、压缩样式信息,引用插件到 plugins.config.js
> npm install optimize-css-assets-webpack-plugin --save-dev
------------------------------------------------------------------------------------------------------------
......
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
......
plugins.push( new OptimizeCssAssetsPlugin({ assetNameRegExp: /\.css$/g, canPrint: true }) );
module.exports = plugins;
------------------------------------------------------------------------------------------------------------
# 在我们使用 transform 2D 转换样式时,由于各个浏览器的兼容性问题,需要给样式添加前缀来修饰,就需要引入 postcss-loader 预处理器来处理兼容样式问题
# 安装 postcss-loader 预处理器,来处理样式兼容性问题,并配置 rule.config.js 样式兼容设置
> npm install postcss-loader@3.x autoprefixer@9.x --save-dev
------------------------------------------------------------------------------------------------------------
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const extract = { loader: MiniCssExtractPlugin.loader, options: { publicPath: '../' } };
const plugins = [ require("autoprefixer")({ overrideBrowserslist: ['last 30 versions', "> 2%", "Firefox >= 10", "ie 9-11"] }) ];
const postcss = { loader: "postcss-loader", options: { plugins } };
module.exports = [
......
{ test: /\.(c|le)ss$/, use: [ extract, 'css-loader', postcss, 'less-loader' ] },
];
webpack 静态资源
网页中同样存在大量的静态资源:图片,在 src/index.html 引入图片静态资源甚至在 CSS 中引入背景图片,一般不直接在 HTML 中使用 img 标记来添加,而是通过异步的方式,这样处理的方式,利于页面加载,具体方法如下:
# 通过一个 DIV 初定一个图片盒子,设置其宽、高,和背景颜色:background-color: #eee;
------------------------------------------------------------------------------------------------------------
<%= require('./components/html-head.ejs')({htmlWebpackPlugin}) %>
<div class="async-image">
</div>
<%= require('./components/html-foot.ejs')({htmlWebpackPlugin}) %>
------------------------------------------------------------------------------------------------------------
#root {
width: 100%; overflow: hidden;
.async-image { width: 683px; height: 300px; background-color: #eee; }
}
------------------------------------------------------------------------------------------------------------
# 通过在 src/js/index.js 中引入图片,得到图片信息,再基于 DOM 的操作,添加引入图片
------------------------------------------------------------------------------------------------------------
import '../css/index.less';
const imagePath = require('../imgs/webpack.png'); // 这个图片自行网上查找,随便一张图片
const image = document.createElement('img');
image.setAttribute('src', imagePath);
document.getElementsByClassName('async-image')[0].appendChild(image);
------------------------------------------------------------------------------------------------------------
# 安装 file-loader 和 url-loader 处理页面中的图片引用,file-loader 用于输入静态资源,url-loader 用于约束图片,小图片转 base64
> npm install file-loader url-loader --save-dev
# 一定要安装 file-loader,不能超出 url-loader 设定约束大小的图片不会拷贝,设置 rule.config.js 如下
------------------------------------------------------------------------------------------------------------
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
......
const options = { name: '[name].[ext]', limit: 8 * 1024, outputPath: 'imgs', esModule: false };
module.exports = [
......
{ test: /\.(png|jpg|jpeg|gif)$/, loader: 'url-loader', options },
{ test: /\.(ttf|eot|woff|woff2|svg)$/, loader: "file-loader", options: { name: '[name].[ext]', outputPath: "fonts" }},
];
webpack 语法转换
随着前端知识的迭代更新,JS 从 es 5~es 6 进步了,甚至 es 7 的地步,JS 越来越完善了,提供了许多 API 方便实际开发应用,当然对于某些不更新的旧版本浏览器的兼容性就没有那么友好了,特别是 IE 浏览器(_~~~ IE 浏览器已经放弃了 Trident 内核更新)
# 在 src/js/index.js 引用 ES 6 的解构方法,进行打包后,分别使用谷歌、IE 浏览器打开 dist/index.html
------------------------------------------------------------------------------------------------------------
let [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 谷歌:1 2 3 IE:SCRIPT1010: 缺少标识符
------------------------------------------------------------------------------------------------------------
# 通过安装 babel-loader、@babel/core、@babel/preset-env 对 ES 6 语法转换,引入 JS 预处理器到 rule.config.js
> npm install babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime --save-dev
------------------------------------------------------------------------------------------------------------
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
......
const presetenv = { presets: ["@babel/preset-env"], plugins: ['@babel/plugin-transform-runtime'] };
module.exports = [
......
{ test: /\.?js/, loader: 'babel-loader', options: presetenv, exclude: /(node_modules|bower_components)/ },
];
------------------------------------------------------------------------------------------------------------
# 在 src/js/index.js 引用 ES 6 的 Promise 异步请求,进行打包后,谷歌浏览器打印信息,而 IE 浏览器返回错误:SCRIPT5009: “Promise”未定义
------------------------------------------------------------------------------------------------------------
function Log () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('随便什么数据');
}, 2000);
});
}
async function Sum () {
let a = 1, b = 2;
let msg = await Log();
console.log(a + b); // 需要等 2 秒后同步 msg 执行
console.log(msg);
}
Sum();
------------------------------------------------------------------------------------------------------------
# 可以通过安装 @babel/polyfill 来全局注入到每个入口文件 entry.config.js 中,但这个会导致打包速度变慢,而且 @babel/polyfill 维护者已经废弃了
> npm install @babel/polyfill --save-dev # 学习参考作用,学完 npm uninstall @babel/polyfill --save-dev 卸载掉
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const routes = require('./route.config');
let entry = {};
routes.map(item => {
const index = item.path.indexOf('/') > -1 ? item.path.replace(/\//g, '.') : item.path;
// 学完还原 entry[index] = resolve(__dirname, './src', `js/${item.path}.js`);
entry[index] = ['@babel/polyfill', resolve(__dirname, './src', `js/${item.path}.js`)];
});
module.exports = entry;
------------------------------------------------------------------------------------------------------------
# 推荐使用按需引入的方式去,按 @babel/polyfill 维护者提议安装 core-js 依赖配置到 rule.config.js 设置按需加载依赖
> npm install core-js --save
------------------------------------------------------------------------------------------------------------
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
......
const presetenv = { presets: [["@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3 }]], plugins: ['@babel/plugin-transform-runtime'] };
module.exports = [
......
];
webpack 库的引用
纵观 src/js/index.js 还是使用的原生方式对 DOM 的进行操作,这种方式虽然能够呈现出效果,但不利于开发效率,在这个前端库泛滥的时代,jquery 依然被淘汰
# 通过安装 vue 2.x 版本,来引入 vue,对比 jquery 库的大小显然 vue 要小的多啦,当然也可以选择其他库,例:react、angular 等
# 通过在 JS 文件中引入 Vue 对象,再进行实例化即可,推荐 const Vue = require('vue/dist/vue'); 方便 Vue 的共享,编辑 src/js/index.js 如下
> npm install vue@2.x --save
------------------------------------------------------------------------------------------------------------
import '../css/index.less';
const Vue = require('vue/dist/vue');
const imagePath = require('../imgs/wKgADlyIZQGAAr6UAAAgYk-FQqs147.jpg');
new Vue ({
el: '#root',
data: { imagePath }
});
------------------------------------------------------------------------------------------------------------
# 需要注意的是 vue 对 IE 的底版本不兼容,紧紧维持在 IE 9+ 以上版本
webpack 服务运行
前端工程化除了能够实现组件化开发,每次调整代码都要去 npm run build 就显得非常麻烦,那么,通过 webpack-dev-server
# 通过安装 webpack-dev-server 来启动一个本地服务,方便项目边改边看,提高开发效率,配置 webpack.config.js 服务和设置 package.json 启动指令
> npm install webpack-dev-server --save-dev
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const entry = require('./entry.config');
const plugins = require('./plugin.config');
const rules = require('./rule.config');
module.exports = {
entry,
output: {
filename: 'js/[name].js',
path: resolve(__dirname, './dist/')
},
module: {
rules: [ ...rules ],
},
plugins: [ ...plugins ],
devServer: {
contentBase: "dist", // 设置服务器主文件,默认:index.js index.html
host: "localhost", // 设置服务器 IP 地址
port: 3000, // 设置服务器端口号
progress: true, // 开启运行进度输出到控制台
compress: true, // 开启一切服务进行 gzip 压缩
hot: true, // 启用热更新
},
mode: 'development'
}
------------------------------------------------------------------------------------------------------------
{
"name": "webp-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack --progress --colors",
"dev": "webpack-dev-server --open --hot --watch",
"test": "echo \"Error: no test specified\" && exit 1"
},
......
}
------------------------------------------------------------------------------------------------------------
# 运行指令 npm run dev 项目自行使用默认浏览器打开 http://localost:3000 本地服务,运行不需要压缩代码,打包需要压缩代码,通过 cross-env 控制
# 通过安装 cross-env 来管理配置运行参数,调整 package.json 文件,以及 webpack.config.js、plugin.config.js
> npm install cross-env --save-dev
------------------------------------------------------------------------------------------------------------
{
......
"scripts": {
"build": "cross-env MODE=production webpack --progress --colors",
"dev": "cross-env MODE=development webpack-dev-server --open --hot --watch",
"test": "echo \"Error: no test specified\" && exit 1"
},
......
}
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const entry = require('./entry.config');
const plugins = require('./plugin.config');
const rules = require('./rule.config');
module.exports = {
......
mode: process.env.MODE // development 开发模式, production 生产模式
}
------------------------------------------------------------------------------------------------------------
......
let plugins = [];
const minify = (process.env.MODE === 'production'); // minify 压缩和清空注释
routes.map(item => {
const chunk = item.path.indexOf('/') > -1 ? item.path.replace(/\//g, '.') : item.path;
plugins.push(
new HtmlWebpackPlugin({
......
chunks: [chunk], inject: true, path:item.path, minify,
......
})
);
});
plugins.push( new MiniCssExtractPlugin({ filename: 'css/[name].css' }) );
const optimize = new OptimizeCssAssetsPlugin({ assetNameRegExp: /\.css$/g, canPrint: true });
process.env.MODE === 'production' && plugins.push(optimize);
module.exports = plugins;
打包的时候,为了防止 dist 存在缓存信息,在打包之前自动执行一次删除 dist 文件,通过 clean-webpack-plugin 插件执行
# 安装 clean-webpack-plugin 插件用于清除打包根目录 dist,防止打包缓存信息,将清理插件引用到 plugin,config.js 中
> npm install clean-webpack-plugin --save-dev
------------------------------------------------------------------------------------------------------------
const { resolve } = require('path');
const routes = require('./route.config');
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
......
let plugins = process.env.MODE === 'production' ? [new CleanWebpackPlugin()] : [];
......
module.exports = plugins;
webpack 跨域处理
前后端分离开发后,就存在着跨端请求后端接口,请求后端提供的接口就需要处理跨域问题,设置 webpack.config.js 的服务映射的 proxy 设置代理
# 为了方便后期维护,在根目录创建 proxy.config.js 用于处理项目跨域代理问题
------------------------------------------------------------------------------------------------------------
let api = process.env.MODE === 'production' ? "/prod-api" : "/dev-api";
const rewite = process.env.MODE === 'production' ? "^/prod-api/" : "^/dev-api/"
module.exports = {
api: {
target: `http://${process.env.SERVE}:${process.env.PORT}`,
pathRewrite: { rewite: "/" },
changeOrigin: true
},
}
------------------------------------------------------------------------------------------------------------
{
"scripts": {
"build": "cross-env SERVE=14.215.177.39 PORT=443 MODE=production webpack --progress --colors",
"dev": "cross-env SERVE=127.0.0.1 PORT=443 MODE=development webpack-dev-server --open --hot --watch",
"describe": "SERVE:服务器地址,PORT:端口"
},
}
webpack 项目实战
{
"name": "webp-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "cross-env SERVE=14.215.177.39 PORT=443 MODE=production webpack --progress --colors",
"dev": "cross-env SERVE=127.0.0.1 PORT=443 MODE=development webpack-dev-server --open --hot --watch",
"describe": "SERVE:服务器地址,PORT:端口"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.13.10",
"@babel/plugin-transform-runtime": "^7.13.10",
"@babel/preset-env": "^7.13.10",
"autoprefixer": "^9.8.6",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"cross-env": "^7.0.3",
"css-loader": "^5.1.2",
"ejs-loader": "^0.5.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.2",
"less": "^4.1.1",
"less-loader": "^7.3.0",
"mini-css-extract-plugin": "^1.3.9",
"optimize-css-assets-webpack-plugin": "^5.0.4",
"postcss-loader": "^3.0.0",
"url-loader": "^4.1.1",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2"
},
"dependencies": {
"core-js": "^3.9.1",
"vue": "^2.6.12"
}
}