?webpack 从零开始搭建 vue-cli?
1、Webpack ??
1.1 webpack 是什么?做了啥?
Webpack 是一个模块打包工具。在开发中,它把一堆文件中每个都作为一个模块处理,找出它们间的依赖关系,并打包成待发布的静态资源。
主要做了:
. 模块化
. 处理不同格式文件,整合
. 优化
列举一个基本例子,设想我们有一堆的 CommonJS 的引用。它们是不能在浏览器直接运行,所以需要把它们 捆绑 成 <script>
标记内的单一文件。Webpack 就能按照 require()
调用的依赖关系为我们做到这点。
实际上,Webpack 能做的更多,通过
"loaders"
我们能让 Webpack 按照我们想要的任何方式打包输出。例如:
- 编译 ES2015、CoffeeScript 或 TypeScript 模块成 ES5 CommonJS 的模块;
- 编译之前,可以通过 linter 校验源代码。
- 编译 Jade 模板成 HTML 并内联 JavaScript 字符串。
- 编译 SASS 文件成 CSS,然后把生成的CSS插入到
<style>
标签内,然后再转译成 JavaScript 代码段。- 处理在 HTML 或 CSS 文件中引用的图片文件,根据配置路径把它们移动到任意位置,根据 MD5 hash 命名。
- 如果你学会了Webpack,就会知道它有多么强大,它非常显著地改善你前端开发的效率。它主要的缺点是配置方式有点麻烦,但是有了我这份使用指南,那使用
Webpack + Vue + vue-loader
的时候,基本上就扫清了大多数障碍了。
1.2 webpack 的前世今生
1.2.1 前端为什么需要模块化
痛点
- 变量和方法不容易维护,容易污染全局作用域
- 加载资源的方式通过script标签从上到下
- 依赖的环境主观逻辑偏重,代码较多就会比较复杂
- 大型项目资源难以维护,特别是多人合作的情况下,资源的引入会让人奔溃
1.2.2 模块化作用
模块化的开发方式可以提供代码复用率,方便进行代码的管理。通常来说,一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数。 有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。
1.2.3 模块规范
但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套!考虑到Javascript模块现在还没有官方规范,这一点就更重要了。 目前流行的js模块化规范有CommonJS、AMD、CMD以及ES6的模块系统。
- CommonJS
NodeJS诞生之后,它使用CommonJS的模块化规范。从此,js模块化开始快速发展。 它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global
- AMD
CommonJS对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。 因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。
AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。 主要有两个Javascript库实现了AMD规范:require.js和curl.js
- CMD
CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。 此规范其实是在sea.js推广过程中产生的。
- ES6
在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
1.3 为什么需要webpack呢??
前端页面效果越来越酷炫、功能越来越复杂。而前端工程师们为了更方便的开发提高开发效率进行了一系列der探索,模块化思想的提出啊,将复杂的程序分割成更小的文件。(模块化)这些年优秀的框架层出不穷react、vue、angular、es6这种在javascript基础上拓展的新的语法规范和 less、sass、css处理器等等等。 所有的事物都是具有双面性的、有利有弊。大大提高开发效率的同时,又为后期维护造成了困扰。因为利用这些工具的文件往往不能直接被浏览器识别,需要手动处理,很影响开发进度(处理整合)。
是否可以有一种方式,不仅可以让我们编写模块,而且还支持任何模块格式(至少在我们到达 ESM 之前),并且可以同时处理资源和资产? 所以webpack应运而生~这就是 webpack 存在的原因。它是一个工具,可以打包你的 JavaScript 应用程序(支持 ESM 和 CommonJS),可以扩展为支持许多不同的静态资源,例如:images, fonts 和 stylesheets。
webpack 关心性能和加载时间;它始终在改进或添加新功能,例如:异步地加载 chunk 和预取,以便为你的项目和用户提供最佳体验。
1.4 webpack相关的安装
pnpm add -D webpack webpack-cli webpack-dev-serve
1.3 webpack-demo 文件目录
|- package.json
|- package-lock.json
|- /dist
|- /public
|-index.html
|- /src
|- index.js
2、node -- path 小知识 ????
2.1 path模块是什么呢?
Node.js path 模块提供了一些用于处理文件路径的小工具,我们可以通过以下方式引入该模块:
const { normalization, resolve, join, extname } = require('path')
2.2 代码如下所示
var path = require("path");
// 格式化路径
console.log('normalization : ' + path.normalize('/test/test1//2slashes/1slash/tab/..'));
// 连接路径(相当于字符串拼接)
console.log('joint path : ' + path.join('/test', 'test1', '2slashes/1slash', 'tab', '..'));
// 转换为绝对路径
console.log('resolve : ' + path.resolve('main.js'));
// 路径中文件的后缀名
console.log('ext name : ' + path.extname('main.js'));
2.3代码执行结果如下
$ node main.js
normalization : /test/test1/2slashes/1slash
joint path : /test/test1/2slashes/1slash
resolve : /web/com/1427176256_27423/main.js
ext name : .js
3、基础配置 ?
3.1创建 webpack.config.js 文件
const { resolve } = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'dist.js', // [name].[contenthash].js
path: resolve(__dirname, 'dist'),
},
devServer: { static: './' },
devtool: 'source-map',
}
3.2 配置package.json -- script
serve: 启动服务
build: 打包
{
// 这里做了一个环境的区分
"serve": "webpack-dev-server --mode=development --hot",
"build": "webpack --mode=production",
}
4、webpack-dev-server -- node服务 ?
4.1 配置
devServer: { static: './' }, // 端口指向当前项目的根路径
// open -- 启动时候打开窗口
// hot -- 热更新
// port -- 端口号
// 更多配置看文档
4.2 运行命令
npx webpack-dev-server
5、plugin插件 ??
5.1 插件作用?
loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。
webpack.config.js
module.exports = {
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};
6、 loader ??
6.1什么是loader?
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。
Warning
webpack 的其中一个强大的特性就是能通过
import
导入任何类型的模块(例如.css
文件),其他打包程序或任务执行器的可能并不支持。我们认为这种语言扩展是很有必要的,因为这可以使开发人员创建出更准确的依赖关系图。
在 webpack 的配置中,loader 有两个属性:
test
属性,识别出哪些文件会被转换。use
属性,定义出在进行转换时,应该使用哪个 loader
webpack.config.js
const path = require('path');
module.exports = {
output: {
filename: 'my-first-webpack.bundle.js',
},
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
// “嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先 use(使用) raw-loader 转换一下。
},
};
7、模式(mode)?
通过选择 development
, production
或 none
之中的一个,来设置 mode
参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为 production
。
webpack.config.js
module.exports = {
mode: 'production',
};
CLI 参数中传递
webpack --mode=development
支持以下字符串值:
选项 | 描述 |
---|---|
development |
会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development . 为模块和 chunk 启用有效的名。 |
production |
会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production 。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin ,FlagIncludedChunksPlugin ,ModuleConcatenationPlugin ,NoEmitOnErrorsPlugin 和 TerserPlugin 。 |
none |
不使用任何默认优化选项 |
如果没有设置,webpack 会给 mode
的默认值设置为 production
。
8、常用插件 ?
8.1 html-webpack-plugin
-
html-webpack-plugin插件的基本作用就是生成html文件
-
webpack-dev-server 则会打开这个html文件,
-
webpack 命令也是打包这个html文件,并将打包后的js引入
-
为html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题
webpack.config.js
plugins: [
new HtmlWebpackPlugin({
// resolve -- 绝对路径,
// __dirname -- 当前文件夹名字,
// 'public/index.html' -- 拼接的路径
template: resolve(__dirname, 'public/index.html'),
}),
],
8.2 VueLoaderPlugin
const { VueLoaderPlugin } = require('vue-loader') // 在 vue-loader 中引入
plugins: [
new VueLoaderPlugin(),
]
这个插件是必须的! 它的职责是将你定义过的其它规则复制并应用到 .vue
文件里相应语言的块。例如,如果你有一条匹配 /\.js$/
的规则,那么它会应用到 .vue
文件里的 <script>
块。
8.2 eslint-webpack-plugin
可以在每次编译时进行 eslint 的语法校验
webpack.congfig.js
new ESLintWebpackPlugin({
fix: true,
}),
9、常用 loader?
9.1 vue-loader
具体配置推荐看:vue-loader 文档地址 ?
Vue Loader 是一个 webpack 的 loader,它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件, 也就是解析以 *.vue 结尾的文件
<template>
<div class="example">{{ msg }}</div>
</template>
<script>
export default {
data () {
return {
msg: 'Hello world!'
}
}
}
</script>
<style>
.example {
color: red;
}
</style>
用途:
- vue-loader 用于识别.vue文件
- vue 不用多说,识别支持vue语法
- vue-template-compiler 语法模板渲染引擎 {{}}
template
、script
、style
安装
// vue-template-compiler 辅助 vue-loader 编译成对应 vue 版本的代码
npm install -D vue-loader vue-template-compiler
这里需要注意的是:
- vue2不能工作在vue-loader17以上,需要降到
npm i -D vue-loader@15.9.8
- vue-template-compiler 版本需要和 vue 版本对应
9.2 babel-loader
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
下面列出的是 Babel 能为你做的事情:
- 语法转换
- 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过引入第三方 polyfill 模块,例如 core-js)
@babel/core:babel转译器本身,提供了babel的转译API,如babel.transform等,用于对代码进行转译。webpack的babel-loader就是调用这些API来完成转译过程的。
// 它会应用到普通的 `.js` 文件
// 以及 `.vue` 文件中的 `<script>` 块
{
test: /\.(js|jsx|ts\tsx)$/,
loader: 'babel-loader',
exclude: 'node_modules/',
},
9.3 vue-style-loader && css-loader
用途:用于处理文件中的 css style
// 它会应用到普通的 `.css` 文件
// 以及 `.vue` 文件中的 `<style>` 块
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader'],
},
9.4 url-loader
用途:url-loader
也是处理图片类型资源,只不过它与file-loader
有一点不同,url-loader
可以设置一个根据图片大小进行不同的操作,如果该图片大小大于指定的大小,则将图片进行打包资源,否则将图片转换为base64
字符串合并到js
文件里
{
test: /\.(png|jpg|jpeg)$/,
use: [
{
loader: "url-loader",
options: {
name: "[name]_[hash:8].[ext]",
limit: 10240, // 这里单位为(b) 10240 => 10kb
// 这里如果小于10kb则转换为base64打包进js文件,如果大于10kb则打包到dist目录
}
}
]
}
9.5 file-loader
用途: 用于处理文件类型资源,如jpg
,png
等图片。返回值为publicPath
为准。
{
test: /\.(png|jpeg|gif|webp)$/,
use: [
{
loader: 'file-loader',
options: {
// 如果打包出现img的src路径为[Object Module],解决方案有
// 将file-loader降级到4.2.0
// 修改options参数esModule为false
esModule: false, // file-loader 默认使用 ES6 模块解析,将其关闭,启用 CommonJS 模块,不配置这个,html 文件中的图片路径不对
name: "img/[name]_[hash:6].[ext]",
publicPath: "http://127.0.0.1:8080" //前缀
},
}
],
},
9.6 less-loader
用途:处理 less 文件。
{
test: /\.less$/,
use: [
'vue-style-loader',
'css-loader',
'less-loader'
]
},
9.7 postcss-loader
用途与插件:
- autoprefixer (添加浏览器前缀)
- precss(可以像写预处理器一样写css)
- cssnext (可以使用最新的css语法)
- cssnano(压缩css)
- stylelint (CSS 检测器,支持新css语法校验)
- postcss-modules
- 过使用 PostCSS 来实现 scpoed
PostCSS 的配置可以通过
postcss.config.js
或postcss-loader
选项来完成。
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader', 'postcss-loader'],
},
10、 loader 具体配置?
10.1 起步
vue-loader、const { VueLoaderPlugin } = require('vue-loader')
10.2 处理资源路径
file-loader
10.3 使用预处理器
less-loader
10.4 Scoped CSS
postcss-loader
10.5 CSS Modules
CSS Modules 是一个流行的,用于模块化和组合 CSS 的系统。vue-loader
提供了与 CSS Modules 的一流集成,可以作为模拟 scoped CSS 的替代方案。
// webpack.config.js
{
module: {
rules: [
// ... 其它规则省略
{
test: /\.css$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
// 开启 CSS Modules
modules: true,
// 自定义生成的类名
localIdentName: '[local]_[hash:base64:8]'
}
}
]
}
]
}
}
对着上面官网的配置会报错 - options has an unknown property 'localIdentName'.
然后我的配置改成这样,便成功编译
{
test: /\.css$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]'
}
}
},
'postcss-loader'
],
},
10.6 热重载
“热重载”不只是当你修改文件的时候简单重新加载页面。启用热重载后,当你修改 .vue
文件时,该组件的所有实例将在不刷新页面的情况下被替换。它甚至保持了应用程序和被替换组件的当前状态!当你调整模版或者修改样式时,这极大地提高了开发体验。
webpack-dev-server --hot
11、 最终代码?
package.json
{
"name": "myvue",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"serve": "webpack-dev-server --mode=development --hot",
"build": "webpack --mode=production",
"lint": "eslint ./src --ext .js,.vue --cache",
"lint:fix": "eslint ./src --ext .js,.vue --fix --cache",
"format": "prettier --write \"./src/*.{html,vue,ts,js,json,md}\""
},
"keywords": [
"vue",
"vue-router",
"keep-alive",
"eslint",
"vue-cli"
],
"author": "jHong",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.12.0",
"babel-loader": "^9.1.2",
"css-loader": "*",
"eslint": "^8.36.0",
"eslint-webpack-plugin": "^4.0.0",
"html-webpack-plugin": "^5.5.0",
"prettier": "^2.8.7",
"vue-loader": "15.9.8",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "^2.7.14",
"webpack": "^5.76.3",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.13.1"
},
"dependencies": {
"vue": "2.6"
}
}
webpack.config.js
const { resolve } = require('path'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
ESLintWebpackPlugin = require('eslint-webpack-plugin'),
{ VueLoaderPlugin } = require('vue-loader')
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].[contenthash].js',
path: resolve(__dirname, 'dist'),
},
plugins: [
new HtmlWebpackPlugin({
template: resolve(__dirname, 'public/index.html'),
}),
new ESLintWebpackPlugin({
fix: true,
}),
new VueLoaderPlugin(),
],
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
},
// 它会应用到普通的 `.js` 文件
// 以及 `.vue` 文件中的 `<script>` 块
{
test: /\.(js|jsx|ts\tsx)$/,
loader: 'babel-loader',
// exclude: 'node_modules',
},
// 它会应用到普通的 `.css` 文件
// 以及 `.vue` 文件中的 `<style>` 块
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader'],
},
// 它会应用到普通的 `.图片` 文件
{
test: /\.(png|jpeg|gif|webp)$/,
use: [
{
loader: 'file-loader',
options: {
// 如果打包出现img的src路径为[Object Module],解决方案有
// 将file-loader降级到4.2.0
// 修改options参数esModule为false
esModule: false, // file-loader 默认使用 ES6 模块解析,将其关闭,启用 CommonJS 模块,不配置这个,html 文件中的图片路径不对
name: "img/[name]_[hash:6].[ext]",
publicPath: "http://127.0.0.1:8080" //前缀
},
}
],
},
],
},
devServer: { static: './' },
devtool: 'source-map',
resolve: {
alias: {
//告诉webpack, @ 符号表示 src目录
'@': resolve(__dirname, './src/'),
'vue$': 'vue/dist/vue.runtime.common.js',
// 'vue$': 'vue/dist/vue.common.js',
},
},
}
12、 总结?
- 运行时版本没有编译器(打包后体积更小),需要配合loader, 处理成 render。
- vue-cli 就是 webpack 配置好各种 loader、plugins 的一个产物,它可以做到开箱即用。