面试总是问 Webpack
怎么样?有什么用?有几个 loader
,有几个 plugin
,结果一去写业务,全都是上的 Vue CLI
,create-react-app
,启动配置什么的,前辈们都配好了
所以这篇文章就是教学如何基于 Webpack
从 0
到 1
启动一个 Vue
项目,下面是这个项目运行效果
传统启动
如果你是刚开始接触 HTML/CSS/JavaScript
三件套开始接触的前端,那么你可能比较熟悉或者比较能接受的引入 Vue
的方式可能是使用 CDN
的方式,大概如下(下面这个是我要介绍的例子)
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<div id="app">
<div>
{{ "count:" + count }}
<button @click="handleClick">click + 1</button>
</div>
</div>
</body>
<script>
new Vue({
data() {
return {
count: 0,
}
},
methods: {
handleClick() {
this.count++;
},
}
}).$mount('#app');
</script>
但如果你继续深入学习的话或者看过一些学习视频,往往他们会推荐你去使用 Vue CLI
去开发 Vue
项目,因为具有一定规模的项目是需要这类脚手架的,它集成了很多功能,但是如果让你自己扔掉 Vue CLI
你能否能够在浏览器中运行呢?因为究其本质,Vue CLI
是基于 node
和 npm
运作的,接下来就教你们一步步简单运行一个 Vue
项目
Webpack 启动
npm 初始化
先找个位置并在终端(或者命令行)初始化一个项目
npm init
初始化后会有一些选项,可以直接回车全部忽略,也可以根据自己意向填写
选择完成之后
这个时候系统会创建一个 package.json
文件
接下来要做的就是安装 Vue
# 文章使用的 Vue2 运行
npm install vue@2.7.10
然后你的 package.json
会有多出一个 dependencies
属性,如下
{
"dependencies": {
"vue": "^2.7.10"
}
}
所以上面的例子需要改成如下写法
// <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
import Vue from "vue"
但这种写法在 HTML
文件需要使用 module
加持,而且现在的 Vue
的位置在 node_modules
,不能再从 HTML
中直接导入,所以需要将上面例子中的 JS
代码脱离,命名为 main.js
import Vue from "vue";
new Vue({
data() {
return {
count: 0,
}
},
methods: {
handleClick() {
this.count++;
},
}
}).$mount('#app');
html
部分命名为 index.html
<body>
<div id="app">
<div>
{{ "count:" + count }}
<button @click="handleClick">click + 1</button>
</div>
</div>
</body>
<script type="text/javascript" src="./main.js" charset="utf-8"></script>
PS: 为什么脚本引入放在了下面?是因为 main.js
里面包含了获取 DOM
的代码,所以为了能够获取到 <div id="app">...</div>
必须放在其下面导入
此时的文件结构
├── node_modules
├── main.js
├── index.html
├── package-lock.json
└── package.json
如果你有过 HTML
部署的经验或者你的电脑上恰好有 Live Server
这个软件,你会发现这个文件结构已经能够运行
但是效果和预期不一样,会是下面这样
为什么显示是 {{"count:" + count}}
而没有被解析呢?
这是因为在 js
中默认导出的 Vue.js
是只包含运行时而不包含编译时的,而编译时干的就是解析的活,具体可以参考这个文档,对不同构建版本的解释 - Vue.js
像 CDN
使用的就是 UMD
完整版的 Vue.js
,而 js
中导出的是只包含运行时的 Vue.js
对于这个问题可以使用 Vue
提供的 h()
渲染函数,像下面这样
new Vue({
// ...
render(h) {
return h("div", [
"count:" + this.count,
h(
"button",
{
on: {
click: this.handleClick,
},
},
"click + 1"
),
]);
},
// ...
}).$mount("#app");
等同于下面的 HTML
<div>
{{ "count:" + count }}
<button @click="handleClick">click + 1</button>
</div>
然后在 package.json
和 html
的导入方式中设置为 module
,也可以生成同样的效果,具体详情就不多演示,因为这涉及到另一个前端工程化的大方向,也就是 Vite
类型的打包框架
当然你也使用 vue-loader
,但是使用 vue-loader
的前提需要一个前提,即 Webpack
Webpack
执行
npm install webpack webpack-dev-server webpack-cli --save-dev
webpack
是一个打包器,可以将不同模块的.js
文件打包成一个.js
文件,就比如上面的Vue.js
和main.js
合并成一个.js
文件webpack-dev-server
可以监视.js
文件内容的修改,进行热更新,具体场景比如更新DOM
的内容,然后更新到浏览器webpack-cli
可以在终端执行webpack
和webpack-dev-server
webpack
重点在于它的配置,配置如下
const webpack = require("webpack");
const path = require("path");
module.exports = {
mode: "development",
devServer: {
static: {
directory: path.join(__dirname, "public"),
},
compress: true,
port: 8080,
},
entry: path.join(__dirname, "./main.js"),
output: {
publicPath: "",
path: path.join(__dirname, "./public"),
filename: "bundle.js",
clean: true,
},
};
我认为 Webpack
的配置是一定要讲的,因为有非常多的属性有默认值,关键是它还比较重要!!
我强烈建议不要随便百度你遇到的问题,请去查看 Webpack 文档,即使它有点烂
解析如下
mode
是webpack
的运行配置entry
是打包的入口文件,这里默认为main.js
output
是打包的出口文件,也就是打包结果path
是打包文件的存放位置,由于我们目的是运行Vue
项目,目的不在打包而是运行,所以不需要过多关注publicPath
需要重点关注,和webpack-dev-server
紧密相连,这个属性目的在于配置使用webpack-dev-server
启动时打包文件的引用位置
devServer
是webpack-dev-server
的配置选项,等同于Live Server
port
启动一个服务在localhost:port
,你可以直接通过http://localhost:port
访问热更新的内容static
directory
是配置静态文件展示的位置的选项,可以简单理解可以直接在浏览器访问在这个位置的index.html
,如上就是http://localhost:8080
配置完 Webpack
,项目的文件也需要重新调整
index.html
需要迁移至public
- 里面的
.js
文件需要改成打包后的文件,即如下
<body>
<!-- ... -->
</body>
<script type="text/javascript" src="./bundle.js" charset="utf-8"></script>
然后还需要修改一下 package.json
的内容,修改如下
{
"name": "vue-start-with-webpack",
"version": "1.0.0",
"description": "运行 vue 项目 demo",
"main": "main.js",
"scripts": {
"start": "webpack server", // 启动 webpack-dev-server
},
"author": "poplink",
"license": "MIT",
"dependencies": {
"vue": "^2.7.10"
},
"devDependencies": {
"webpack": "^5.75.0",
"webpack-cli": "^5.0.0",
"webpack-dev-server": "^4.11.1"
}
}
现在的目录结构如下
├── node_modules
├── public
| └── index.html
├── main.js
├── index.html
├── package-lock.json
├── package.json
└── webpack.config.js
测试运行 npm run start
,结果如下
解析如下
mode
是webpack
的运行配置entry
是打包的入口文件,这里默认为main.js
output
是打包的出口文件,也就是打包结果path
是打包文件的存放位置,由于我们目的是运行Vue
项目,目的不在打包而是运行,所以不需要过多关注publicPath
需要重点关注,和webpack-dev-server
紧密相连,这个属性目的在于配置使用webpack-dev-server
启动时打包文件的引用位置
devServer
是webpack-dev-server
的配置选项,等同于Live Server
port
启动一个服务在localhost:port
,你可以直接通过http://localhost:port
访问热更新的内容static
directory
是配置静态文件展示的位置的选项,可以简单理解可以直接在浏览器访问在这个位置的index.html
,如上就是http://localhost:8080
配置完 Webpack
,项目的文件也需要重新调整
index.html
需要迁移至public
- 里面的
.js
文件需要改成打包后的文件,即如下
<body>
<!-- ... -->
</body>
<script type="text/javascript" src="./bundle.js" charset="utf-8"></script>
然后还需要修改一下 package.json
的内容,修改如下
{
"name": "vue-start-with-webpack",
"version": "1.0.0",
"description": "运行 vue 项目 demo",
"main": "main.js",
"scripts": {
"start": "webpack server", // 启动 webpack-dev-server
},
"author": "poplink",
"license": "MIT",
"dependencies": {
"vue": "^2.7.10"
},
"devDependencies": {
"webpack": "^5.75.0",
"webpack-cli": "^5.0.0",
"webpack-dev-server": "^4.11.1"
}
}
现在的目录结构如下
├── node_modules
├── public
| └── index.html
├── main.js
├── index.html
├── package-lock.json
├── package.json
└── webpack.config.js
测试运行 npm run start
,结果如下
注意,这个时候你可以观察一下目录里面并没有生成 bundle.js
,但 webpack-dev-server
并不是没有打包,而是放在了内存里,只是你没看见,具体可以参考文档
Warning
webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中,然后将它们 serve 到 server 中,就好像它们是挂载在 server 根路径上的真实文件一样。如果你的页面希望在其他不同路径中找到 bundle 文件,则可以通过 dev server 配置中的 [`devMiddleware.publicPath`](https://webpack.docschina.org/configuration/dev-server/#devserverdevmiddleware) 选项进行修改。
这也就是为什么需要强调 publicPath
的重要性,它就是指定了 bundle.js
的引用位置
对于值的取舍可以参考 Webpack 文档
vue-loader 引入
先安装 vue-loader
# 注意:vue-loader@15.9.2 适配 Vue2,不要安装最新版,有坑,运行不起来
# 注意:vue-template-compiler 需要和你的 Vue2 版本一致!
npm install vue-loader@15.9.2 vue-template-compiler@2.7.10 --save-dev
在 webpack.config.js
中添加
const { VueLoaderPlugin } = require("vue-loader");
module.exports = {
// ...
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader",
}
],
},
plugins: [new VueLoaderPlugin()],
};
但到这里其实还没有结束,因为我们的 vue-loader
是针对 .vue
文件的,所以需要将 dom
相关的内容迁移至 .vue
文件里
在 main.js
同目录下创建 App.vue
文件,内容如下
<template>
<div>
{{ "count:" + count }}
<button @click="handleClick">click + 1</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
};
},
methods: {
handleClick() {
this.count++;
},
},
};
</script>
<style></style>
main.js
的内容修改为
import Vue from "vue";
import App from "./App.vue";
new Vue({
render: (h) => h(App),
}).$mount("#app");
index.html
的内容修改为
<body>
<div id="app"></div>
</body>
<script type="text/javascript" src="bundle.js" charset="utf-8"></script>
再次运行 npm run start
结果如下
总结
.js
到 .vue
的操作就是这么简单,但好像也没用几个 loader
注意,这个时候你可以观察一下目录里面并没有生成 bundle.js
,但 webpack-dev-server
并不是没有打包,而是放在了内存里,只是你没看见,具体可以参考文档
Warning
webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中,然后将它们 serve 到 server 中,就好像它们是挂载在 server 根路径上的真实文件一样。如果你的页面希望在其他不同路径中找到 bundle 文件,则可以通过 dev server 配置中的 [`devMiddleware.publicPath`](https://webpack.docschina.org/configuration/dev-server/#devserverdevmiddleware) 选项进行修改。
这也就是为什么需要强调 publicPath
的重要性,它就是指定了 bundle.js
的引用位置
对于值的取舍可以参考 Webpack 文档
vue-loader 引入
先安装 vue-loader
# 注意:vue-loader@15.9.2 适配 Vue2,不要安装最新版,有坑,运行不起来
# 注意:vue-template-compiler 需要和你的 Vue2 版本一致!
npm install vue-loader@15.9.2 vue-template-compiler@2.7.10 --save-dev
在 webpack.config.js
中添加
const { VueLoaderPlugin } = require("vue-loader");
module.exports = {
// ...
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader",
}
],
},
plugins: [new VueLoaderPlugin()],
};
但到这里其实还没有结束,因为我们的 vue-loader
是针对 .vue
文件的,所以需要将 dom
相关的内容迁移至 .vue
文件里
在 main.js
同目录下创建 App.vue
文件,内容如下
<template>
<div>
{{ "count:" + count }}
<button @click="handleClick">click + 1</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
};
},
methods: {
handleClick() {
this.count++;
},
},
};
</script>
<style></style>
main.js
的内容修改为
import Vue from "vue";
import App from "./App.vue";
new Vue({
render: (h) => h(App),
}).$mount("#app");
index.html
的内容修改为
<body>
<div id="app"></div>
</body>
<script type="text/javascript" src="bundle.js" charset="utf-8"></script>
再次运行 npm run start
结果如下
最后检查一下热更新
最后的文件目录如下,以及 github
的例子请参考 pandoralink/vue-start-with-webpack - github
├── node_modules
├── public
| └── index.html
├── App.vue
├── main.js
├── package-lock.json
├── package.json
└── webpack.config.js
总结
刚刚接触 Vue CLI
的同学可以对比一下 Vue CLI
创建的项目和本篇文章的文件目录对应的文件比较,对应 Vue CLI
部分原理
_PS: .js
到 .vue
的操作就是这么简单,但好像也没用几个 `loader