本文为 Vue 学习系列笔记第一篇,将持续更新。
前端工程化指的是:在企业级的前端项目开发中,把前端开发所需的工具、技术、流程、经验等规范化、标准化。
企业中 Vue 项目 和 React 项目,都是基于工程化的方式进行开发的。这使得前端开发自成体系,有一套标准的开发方案和流程。
而为了让前端开发能够 “ 自成体系 ”,需要从下面四个方面进行考虑:
更多介绍可参考此文:前端工程化的理解
早期的前端工程化解决方案(目前已被边缘化):
目前主流的工程化解决方案:
本篇文章主要介绍 webpack 如何解决前端工程化问题。
Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
它提供了友好的前端模块化开发支持,以及 代码压缩混淆、处理浏览器 JavaScript 的兼容性、性能优化 等强大的功能。有效地提高了开发效率和项目的可维护性。
具体步骤如下:
npm init –y
命令,初始化包管理配置文件 package.json
index.html
首页和 src -> index.js
脚本文件npm install jquery –S
命令,安装 jQuery但是打开 index.html 页面时,你会发现一个问题:它报错了!
出现了兼容性问题,那么怎么解决呢?这就要用到 webpack。下面我们先来安装 webpack,再讲解如何解决此问题。
在终端运行如下命令,安装 webpack 相关的两个包:
npm install --save-dev webpack
说一下 package.json 中 dependencies 和 devDependencies 的区别
- dependencies:应用能够正常运行所依赖的包。无论是开发、还是上线发布都要用到的 npm 包放在这里。
用法:--save
,可简写为-S
- devDependencies: 开发应用时所依赖的工具包。仅在开发时会用到的 npm 包放在这里。
用法:--save-dev
,可简写为-D
① 在项目根目录中,创建名为 webpack.config.js 的 webpack 配置文件,并初始化如下的基本配置:
module.exports = {
mode: 'development'
}
注意:mode
用来指定构建模式。可选值有 development
(开发阶段) 和 production
(上线阶段)。
② 在 package.json 的 scripts 节点下,新增 dev 脚本(最好命名为 dev,当然你也可以指定其他名称)
"scripts": {
"dev":"webpack"
}
注意:script 节点下的脚本,可以通过 npm run 执行,如 npm run dev
。
③ 在终端中运行 npm run dev
命令,启动 webpack 进行项目的打包构建。
④ webpack 打包构建完成后,会自动生成 dist 文件夹,里面含有文件 main.js。在 index.html 中导入此模块文件 main.js,即可解决之前导入模块文件遇到的兼容性问题等。
再说一下 main.js 文件,它相当于将此前有兼容性问题的模块文件 index.js 和 jquery.js 进行了合并,通过此前 webpack 打包后的终端显示的结果也可看出这一点。
development
production
webpack.config.js 是 webpack 的配置文件。webpack 在真正开始打包构建之前,会先读取这个配置文件,从而基于给定的配置(mode
),对项目进行打包。
注意:由于 webpack 是基于 node.js 开发出来的打包工具,因此在它的配置文件中,支持使用 node.js 相关的语法和模块进行 webpack 的个性化配置。
上面一系列的操作看似没有什么破绽,可是你有没有想到一个问题,那就是 webpack 怎么知道要去打包 src / index.js 文件呢?
这是因为在 webpack 中有如下的默认约定:
注意:可以在 webpack.config.js 中修改打包的默认约定。
在 webpack.config.js 配置文件中,通过 entry
节点指定打包的入口。通过 output
节点指定打包的出口。
例如,我们打算将 ./src/index1.js 文件进行打包,输出到 dist 文件夹中,名称为 bundle.js 。如下:
module.exports = {
mode: 'development',
entry: './src/index1.js', // 打包文件入口的路径
output: {
path: path.join(__dirname, 'dist'), // 输出文件存放路径
filename: 'bundle.js' // 输出文件的名称
}
}
接下来我们又面临一个问题,难道说每次修改完源代码,都要重新打包文件吗?这也太麻烦了吧。幸运的是,这里可以安装一个插件 webpack-dev-server 来解决这个问题,使得打包文件会自动更新。
它类似于 node.js 中的 nodemon 工具。每当修改了源代码,webpack 会自动进行项目的打包和构建。
安装命令如下:
npm install webpack-dev-server --save-dev
① 修改 package.json 文件中的 "scripts"
"scripts": {
"dev": "webpack serve"
}
② 再次运行 npm run dev
命令,重新进行项目的打包
通过终端输出结果可以看到:webpack output is served from /
,意思是说 output 文件被生成在了文件根目录下面,但是我们并没有在根目录下面发现指定的 output 文件 bundle.js。
这是因为,webpack-dev-server 插件将新生成的 bundle.js 放在了内存中,而非物理磁盘中,因此我们看不到。放在内存中的好处有两个:速度快、防止频繁读写硬盘导致寿命损耗。
同时,在 html 文件中引入时,我们也要引入最新的 bundle.jsj 文件。需要进行如下修改:
<script src="/bundle.js">script>
③ 在浏览器中访问 http://localhost:8080 地址,查看自动打包结果
这样,在修改完源代码后,无需重启服务器,也无需手动刷新页面,就可以得到最新的页面效果。
又有一个问题,每次访问 index.html 页面,需要打开 http://localhost:8080 后再点击 src 文件夹才能访问,太过复杂。
这里可以利用 html-webpack-plugin 插件,它可以将 src 文件夹中的 index.html 复制一份放在根目录 / 下面。这样以后只要打开 http://localhost:8080 页面,即可访问到 index.html 。
安装命令如下:
npm i --save-dev html-webpack-plugin
在 webpack.config.js 文件中,进行如下配置操作:
// 1. 导入 html-webpack-plugin 这个插件,得到插件的构造函数
const HtmlPlugin = require('html-webpack-plugin');
// 2. new 构造函数,创建插件的实例对象
const htmlPlugin = new HtmlPlugin({
template: './src/index.html', // 指定要复制的文件
filename: './index.html' // 指定复制文件的名称和目的路径
});
module.exports = {
mode: 'development',
// 3. 插件的数组,将来 webpack 在运行时,会加载并调用这些插件
plugins: [htmlPlugin]
}
这样,打开 html://localhost:8080 就会加载出 index.html 显示的页面:
注意:这里 html-webpack-plugin 插件的使用还需强调两点:
在 webpack.config.js 配置文件中,可以通过 devServer 节点对 webpack-dev-server 插件进行更多的配置。
比如,你懒得每次还需要手动打开 html://localhost:8080 页面,想让它配置之后自动打开浏览器显示该页面,你就可以这样配置:
devServer: {
open: true, // 首次打包成功后,自动打开浏览器
port: 80, // 指定端口。在 http 协议中,端口号 80,可以被省略
host: '127.0.0.1' // 指定运行的主机地址
}
在实际开发过程中,webpack 默认只能打包处理以 .js
为后缀名的模块。其他非 .js
后缀名结尾的模块,webpack 默认处理不了,需要调用 loader 加载器才能正常打包,否则会出现报错。
loader 加载器的作用:协助 webpack 打包处理特定的文件模块。如下:
css-loader
:可以打包 .css
相关的文件less-loader
:可以打包处理 .less
相关的文件babel-loader
:可以打包处理 webpack 无法处理的高级 JS 语法在 webpack 中,一切皆模块,都可以通过 ES6 导入语法进行导入和使用。例如,我们要引入 index.css 文件,可以在 webpack 入口文件 index.js 中进行如下操作:
但是你立刻会发现,它报错了!
因为 webpack 只能处理 .js
文件,这里需要利用第三方的 loader 加载器。
怎么配置对应 loader 呢 ?
① 运行 npm i style-loader css-loader -D
进行安装处理 css 文件的 loader
② 在 webpack.config.js 中做出如下配置:
module: {
rules: [
// 处理 .css 文件的 loader
{
test: /\.css$/, use: ['style-loader', 'css-loader'] },
]
}
其中,test
表示匹配的文件类型, use
表示对应要调用的 loader 。
注意:use 数组中指定的 loader 顺序是固定的。这是因为当多个 loader 的调用时,其调用顺序是:从后往前调用 。
① 运行 npm i less-loader less -D
安装处理 less 文件的 loader
② 在 webpack.config.js 中做出如下配置:
module: {
rules: [
// 处理 .less 文件的 loader
{
test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
]
}
在此之前先说一下 base64 格式的图片(base64在线转换工具)
通过下面两种方式显示出的图片是一样的:
它们的区别在于:使用前者时,浏览器在加载完 HTML 标签后,还需要发送额外请求才能得到该图片并进行渲染。而第二种方式,浏览器不用再请求服务器调用图片资源,在请求标签时就将该图片请求下来,减少了服务器访问次数。
但是它有个小缺点,会增加 css 的体积或者存储在数据库中增大了数据库服务器的压力。不适用于大图片的转换。
这里我们有个需求,打算设置 index.html 文件中 标签的属性:
这里 webpack 又遇到难题了,无法引入 .png
文件,这里又需要借助 loader 加载器:
怎么配置对应 loader 呢 ?
① 运行 npm i url-loader file-loader -D
命令安装对应处理 url 的 loader
② 在 webpack.config.js 中做出如下配置:
module: {
rules: [
// 处理图片类型文件
{
test: /\.jpg|png|gif$/, use: 'url-loader?limit=22229' }
]
}
其中 ?
之后的是 loader 的参数项:
limit
用来指定图片的大小,单位是字节(byte)可以看到引入的 logo.png 图片就被转换为了 base64 格式:
webpack 只能打包处理一部分高级的 JavaScript 语法。而对于一些 webpack 无法处理的语法,需要借助于 bable-loader 进行打包处理。
例如 webpack 无法处理下面的 js 代码:
怎么配置 babel-loader 呢?
① 执行下面代码进行安装
npm i babel-loader @babel/core @babel/plugin-proposal-decorators -D
② 在 webpack.config.js 中做出如下配置:
module: {
rules: [
// 使用 babel-loader 处理高级 JS 语法
// 注意:必须使用 exclude 排除项。因为 node_modules 目录下的第三方包不需要打包
{
test: /\.js$/, use: 'babel-loader', exclude: /node_module/ },
]
}
③ 在项目根目录下,创建名为 babel.config.js 的配置文件,定义 Babel 的配置项 如下:
module.exports = {
// 声明 babel 可以的插件
// webpack 在调用 babel-loader 的时候,会先加载 plugins 插件来使用
plugins: [['@babel/plugin-proposal-decorators', {
legacy: true }]]
}
项目开发完成之后,使用 webpack 对项目进行打包发布的主要原因有以下两点:
为了让项目能够在生产环境中高性能的运行,因此需要对项目进行打包发布。
在 package.json 文件的 script 节点下,新增 bulid 命令:
"scripts": {
"dev": "webpack serve",
"build": "webpack --mode production"
}
项目发布时,执行 build 命令: npm run build
。生成 dist 文件,压缩此文件发给后端人员就 OK 了。
注意:
--model
是一个参数项,用来指定 webpack 的运行模式。production
代表生产环境,进行代码压缩和性能优化。--model
指定的参数项,会 覆盖 webpack.config.js 中的 model 选项。
直接将各种杂乱的文件打包肯定是不行的,这里我们来进行优化一下,将 Javascript 文件统一生成到 js 目录中。具体操作:
在 webpack.config.js 配置文件的 output 节点中,进行如下配置:
在 webpack.config.js 配置文件的 module => rules 节点中,进行如下配置:
// 处理图片类型文件,多个参数可用 & 分隔
{
test: /\.jpg|png|gif$/, use: 'url-loader?limit=229&outputPath=images' }
为了每次打包发布时能够 自动清理掉 dist 目录中的文件夹,可以安装配置 clean-webpack-plugin 插件
npm i clean-webpack-plugin -D
接下来如何配置呢 ?
在 webpack.config.js 配置文件中先引入:
// 解构赋值
const {
CleanWebpackPlugin } = require('clean-webpack-plugin');
然后,在 plugins 节点(数组)中添加:
new CleanWebpackPlugin()
可参考对应的 npm 官方文档查看具体描述 => clean-webpack-plugin
前端项目在投入生产环境之前,都需要对 JavaScript 源代码进行压缩混淆,从而减小文件的体积,提高文件的加载效率。
此时就不可避免的产生了另一个问题:对压缩混淆之后的代码 除错(debug)是一件极其困难的事情。
因为,通常 JavaScript 的解释器会告诉你,第几行第几列代码出错。但是,这对于转换后的代码毫无用处。
而要成功解决这个问题就要用到这里所要介绍的 Source Map 。
Source Map 就是一个信息文件,里面储存着位置信息。也就是说,Source Map 文件中存储着代码压缩混淆前后的对应关系。
有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码,能够极大的方便后期的调试。
在开发环境下,webpack 默认启用了 Source Map 功能。当程序运行出错时,可以直接在控制台提示错误行的位置,并定位到具体的源代码。
但是,开发环境下默认生成的 Source Map 记录的是 生成后的代码的位置。会导致运行时报错的行数与源代码的行数不一致的问题。
开发环境下,推荐在 webpack.config.js 中添加如下的配置,即可 保证运行时报错的行数与源代码的行数保持一致:
module.exports = {
// 在开发调试阶段,建议将 devtool 做如下设置
devtool: 'eval-source-map',
...
}
在生产环境下,最好省略 devtool
选项,使得最终生成的文件中不包含 Source Map。这能够防止原始代码通过 Source Map 的形式暴露给别有所图之人,提高安全性。
为了兼顾安全性同时又想定位具体行号,也可以有个折中方案。
在生产环境下,如果 只想定位报错的具体行数,且不想暴露源码。可以在 webpack.config.js 中这样配置:
module.exports = {
// 定位源码行号,但不暴露源码
devtool: 'nosources-source-map',
...
}
开发环境下:
devtool
的值设置为 eval-source-map生产环境下:
devtool
的值设置为 nosources-source-map在引入文件时如果这个文件藏得很深,你不得不去这样引入:
import msg from '../../msg'
那么多的 ../
是不是看上去都有些眼花缭乱 ?而如果利用 @
,你就可以解决这个烦恼,如下:
import msg from '@/msg'
但是,使用 @
时需要在 webpack.config.js 中进行配置:
module.exports = {
...
resolve: {
alias: {
// @ 表示 src 这一层目录
'@': path.join(__dirname, './src/')
}
}
}
写在最后
其实在实际开发中根本不需要自己去配置 webpack。实际开发中会使命令行工具( CLI )一键生成带有 webpack 的项目,也就是开箱即用,所有配置都是现成的。但是,如果你能搞清其中原理当然是有百利而无一害的。