- 参考视频:https://www.bilibili.com/video/av89760569
- 源码和markdown下载:https://github.com/tanglei302wqy/notes/tree/master/%E5%89%8D%E7%AB%AF/vue
- 欢迎star、fork、follow素质三连
注意:这一章中所有项目源码的node_modules需要重新生成。
JS 最开始时,没有模块化开发概念,随着前端项目越来越复杂,非模块化开发带来两个主要问题:
在闭包中使用一个 Object 对象,并返回该 Object 对象。在别的文件中可以通过返回的 Object 对象引入需要的变量、函数等。
根据原始解决思路,前端开发中已经有相关的实现产品:CommonJS 和 ES6 模块化:
// 导出foo.js文件
module.exports = {
flag: true,
test(a, b) {
return a + b;
}
};
// 导入foo.js文件
let { flag, test } = require("foo.js");
// index.html
<script src="./foo.js" type="mudule"></script>
<script src="./bar.js" type="mudule"></script>
// foo.js
let flag = true;
// 直接导出
export let ping = "pong";
function sum(a, b) {
return a + b;
}
export {flag, sum}
// bar.js
import {flag, sum} from "./foo.js"
if (flag) {
console.log(sum(1, 2));
}
export default
:某些情况下,一个模块包含某个功能,并不希望给这个功能命名,而是让导入者可以自己来命名,这个时候就可以使用 export default。注意一个 mudule 中,只能有一个 export default:// info.js
export default function() {
console.log("default function()");
}
// other.js导入function()并重新命名为myFunc
import myFunc from "./info.js";
import * as dummy from "./foo.js";
if (dummy.flag) {
//...
}
webpack 一个核心就是让我们尽可能模块化开发,并且会帮助我们处理模块化的依赖关系。而且不仅仅是 JS 文件,CSS、图片、JSON 文件等在 webpack 中都可以被当做模块来使用。
webpack 会将各种资源模块进行打包合并成一个或者多个包(Bundle),并且在打包过程中还会进行处理,例如将 es6 的语法转换成 es5 的语法,将 TypeScript 转换成 JS 等等。
webpack 依赖 node.js,node.js 自带的软件包管理工具 npm。
node -v
v12.16.0
npm -v
6.13.4
// 全局安装
npm install [email protected] -g
// 局部安装
// 终端里直接执行webpack命令是全局安装,当在package.json中定义了
// scripts时,其中包含了webpack命令,那么使用的是局部webpack
npm install [email protected] --save-dev
webpack -v
3.6.0
1_简单使用
|
+- index.html
|
+- dist
| |
+ +- bundle.js(webpack命令生成)
|
+- src
|
+- main.js(通过CommonJS和es6语法导入mathUtil.js和info.js)
|
+- mathUtil.js
|
+- info.js
/* mathUtil.js 使用CommonJS导出方式 */
function add(n1, n2) {
return n1 + n2;
}
module.exports = {
add
};
/* info.js 使用es6导出方式 */
export let name = "sherman";
export let age = "23";
export let height = "1.76";
// CommonJS导入方式
let { add } = require("./mathUtil.js");
// es6导入方式
import { name, age, height } from "./info.js";
console.log(name, age, height);
console.log(add(1, 2));
webpack src\main.js dist\bundle.js
<html>
<title>webpack简单使用title>
<head>
<meta charset="utf-8" />
head>
<body>
<script src="./dist/bundle.js">script>
body>
html>
在上面使用 webpack 进行打包时的用法为:webpack src dst
,现在希望直接使用一个 webpack 命令就能打包:
node init
webpack.config.js
,在该文件中定义入口和出口:const path = require("path");
module.exports = {
entry: "./src/main.js",
// 出口需要以path和filename形式存在
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
}
};
webpack
即可完成项目打包使用node init
命令对项目初始化时,会创建一个 package.json 文件,该文件中有一个 scripts 属性,可以配置命令映射:
{
"name": "meetpackage",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"//": "执行build命令相当于执行webpack命令",
"build": "webpack"
},
"author": "",
"license": "ISC"
}
在命令行中输入npm run build
进行打包。
在 package.json 中配置的 webpack 映射和直接使用 webpack 命令主要的区别:package.json 中配置的映射会优先在本地 webpack 中查找,直接使用 webpack 查找的是全局 webpack。
除非一层一层进入 node 目录下 webpack 的目录(node_mudule/.bin/webpack
),然后在该目录下执行 webpack 才会使用本地 webpack。
安装本地 webpack:
npm install [email protected] --save-dev
--save-dev
代表开发时依赖,webpack 只是在打包时候有用,打包之后的项目不需要在依赖 webpack,因此可以选择开发时依赖安装本地 webpack。
安装完成后:
devDependencies
属性node_modules
目录,该目录下存放默认的 node 模块{
"name": "meetpackage",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"//": "执行build命令相当于执行webpack命令",
"build": "webpack"
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^3.6.0"
}
}
在实际开发中,不仅有基本的 JS 代码处理,还需要处理 CSS、图片,也包括将 ES6 语法转换成 ES5 语法,将 TypeScript 转换成 ES5 代码,将 SCSS、LESS 转换成 CSS,将.jsx、.vue 装换成 js 文件等等。
webpack 本身对于这些转换并不支持,需要使用 loader 进行拓展。
require("./css/foo.css");
const path = require("path");
module.exports = {
entry: "./src/main.js",
// 出口需要以path和filename形式存在
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
},
module: {
rules: [
{
test: /\.css$/,
// css-loader只负责将css文件进行加载
// style-cloader负责将样式加入到DOM中
// 使用多个loader时候,读取顺序是从右往左进行的
use: ["style-loader", "css-loader"]
}
]
}
};
num run build
进行打包和使用 CSS 配置相同,首先需要在 main.js 中引入对应的.less 文件,然后安装 less-loader,注意同时需要安装 less 包(less 包是 npm 包下的一部分,用于将 less 文件解析成 css 样式,这个解析过程是 less 完成的,不是 webpack 完成的)。最后需要在 webpack.config.js 文件中配置 less-loader 完成配置(注意 less 和.css 的 loade 不能共用,需要各自配置):
const path = require("path");
module.exports = {
entry: "./src/main.js",
// 出口需要以path和filename形式存在
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
# ...这里省略
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
}
]
}
}
需求:
现在希望利用 CSS 文件将 index.html 的背景图片换成自定义图片。
background: url("..img/tom-big.jpg")
limit
,则会将图片使用 Base64 转换成字符串形式,不会以文件形式保存在 dist/目录下limit
,则会将图片保存在 dist\目录下,并且是 32 位 hash 值命名。npm install url-loader --save-dev
安装相关的 url-loader,如果是 file-loader,不需要额外安装name
:const path = require("path");
module.exports = {
entry: "./src/main.js",
// 出口需要以path和filename形式存在
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: 'dist/'
},
module: {
rules: [
// ...这里省略
{
test: /\.(png|jpg|jpeg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
// 将新的文件放入到dist/img下,并且格式为:
// 原图片名称-8位hash值.原图片拓展名
name: 'img/[name]-[hash:8].[ext]'
}
}
]
}
]
}
}
webpack 将 main.js 及其依赖的 js 文件最终打包成 bundle.js 文件,但是在 bundle.js 文件中,使用的语法还是 es6 的,如果浏览器不支持 es6 语法,就会报错。
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
const path = require("path");
module.exports = {
entry: "./src/main.js",
// 出口需要以path和filename形式存在
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: 'dist/'
},
module: {
rules: [
// ... 这里省略
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
// 这里和安装babel-preset-es2015对应
presets: ['es2015']
}
}
}
]
}
}
npm run build
命令后,可以看到 bundle.js 中例如 const 的 es6 语法全部被替换成 es5 中的 var 语法之前使用 Vue 都是通过
npm install Vue --save
:注意此时不能使用–save-dev,因为项目打包后还是需要 Vue// ...这里省略其它
// 使用Vue
import Vue from 'vue';
const app = new Vue({
el: '#app',
data: {
message: 'msg'
}
})
// index.html
<!DOCTYPE html>
<html>
<title>webpack配置Vue</title>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>webpack的loader</h2>
<div id="app">
{{message}}
</div>
<script src="./dist/bundle.js"></script>
</body>
</html>
直接运行npm run build
报错:
You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
这设计到两种编译方式:
直接使用 npm run build 打包会直接使用 vue-runtime 版本,导致出错
解决:在 webpack.config.js 中加入 resolve 配置:
const path = require("path");
module.exports = {
entry: "./src/main.js",
// 出口需要以path和filename形式存在
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: 'dist/'
},
// ...这里省略
resolve: {
alias: {
// 默认使用的是:node_modules/vue/dist/vue.runtime.js
// 修改成:node_modules/vue/dist/vue.esm.js
'vue$': 'vue/dist/vue.esm.js'
}
}
}
在上面演示过程中,index.html 文件
// main.js
// ...省略一部分内容
import Vue from 'vue';
new Vue({
el: '#app',
template: `
{{message}}
`,
data: {
message: 'msg'
}
})
// index.html
<!DOCTYPE html>
<html>
<title>webpack配置Vue</title>
<head>
<meta charset="utf-8" />
</head>
<body>
<div id="app"></div>
<script src="./dist/bundle.js"></script>
</body>
</html>
上面代码中 Vue 实例的 template 中有 html 代码,随着代码增多会变得非常臃肿,需要进行抽取。
先自定义一个组件,然后将 Vue 实例中的 template 代码放到自定义组件的 template 中,也因为 Vue 实例的 template 中已经全部移到自定义组件中了,那么 Vue 实例中的 data、methods 等都因该同步到自定义组件中。之后 Vue 实例直接注册自定义组件,并且在 template 中引用自定义组件的标签即可:
// ...省略其它内容
// 配置Vue
import Vue from "vue";
// 将原本在Vue实例中的东西先抽取出来,之后在Vue实例中注册App组件即可
const App = {
template: `
{{message}}
`,
data() {
return {
message: "msg"
};
},
methods: {
onClick() {
console.log("click");
}
}
};
new Vue({
el: "#app",
// 在template中用了一个自定义子组件
template: " ",
components: {
App
}
});
上述抽取虽然简化了 Vue 实例的代码,但是自定义组件还是存在于 Vue 实例的文件中,随着自定义组件不断增多,最终 Vue 实例所在的文件还是会变得非常臃肿。
因为自定义组件也是一个对象,既然是对象,就可以导入。因此可以新建一个存放自定义组件的文件夹,每个文件对应一个自定义组件,在 Vue 实例需要的使用,可以直接 import 进来。特别的,因为每个文件代表一个自定义组件,还可以配合 import default 来优化。
export default {
template: `
{{message}}
`,
data() {
return {
message: "msg"
};
},
methods: {
onClick() {
console.log("click");
}
}
};
import App from './vue/app.js'
进行导入// ...省略其它内容
import App from "./vue/app.js";
new Vue({
el: "#app",
// 在template中用了一个自定义子组件
template: " ",
components: {
App
}
});
虽然 app.js 文件表示一个自定义组件,并且存放在 vue 文件夹下,main.js 需要使用的时候直接 import 进来即可。但是 app.js 中,template、data、methods 以及样式并没有进行分离。为此,可以引入.vue 文件来进行分离。
/* 分离模板 */
{{ message }}
/* 分离脚本 */
/* 分离样式 */
npm install vue-laoder vue-template-compiler --save-dev
const path = require("path");
module.exports = {
entry: "./src/main.js",
// 出口需要以path和filename形式存在
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: 'dist/'
},
module: {
rules: [
// ...其它省略
{
test: /\.vue$/,
use: ['vue-loader']
}
]
},
resolve: {
alias: {
// 默认使用的是:node_modules/vue/dist/vue.runtime.js
// 修改成:node_modules/vue/dist/vue.esm.js
'vue$': 'vue/dist/vue.esm.js'
}
}
}
"vue-template-compiler": "^2.6.11"
,之后执行npm install
刷新 package.json 文件变化Vue 抽取最终版本中,main.js 引入 App.vue 文件时,默认不能省略后缀名,需要在 webpack.config.js 中进行配置:
const path = require("path");
module.exports = {
entry: "./src/main.js",
// 出口需要以path和filename形式存在
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: 'dist/'
},
module: {
// ...这里省略
},
resolve: {
alias: {
// 默认使用的是:node_modules/vue/dist/vue.runtime.js
// 修改成:node_modules/vue/dist/vue.esm.js
'vue$': 'vue/dist/vue.esm.js'
},
// 配置哪些文件可以省略后缀名
extensions: ['.js', '.css', '.vue']
}
}
plugin 通常对某个现有的功能进行拓展,plugin 相对于 loader:
可以在 webpack 对项目打包之后的 bundle.js 文件中加入版权信息:
const path = require("path");
const webpack = require('webpack');
module.exports = {
// ...这里省略
plugins: [
new webpack.BannerPlugin('版权信息')
]
}
再使用npm run build
命令打包项目之后,就能在 bundle.js 文件中看到版权信息。
在真实项目发布时,发布的是 dist 文件夹中的内容,但是 dist 文件夹中没有 index.html 文件,那么打包的 js 等文件就没有意义了。
所以,需要使用 HtmlWebpackPlugin 插件将 index.html 文件打包到 dist 文件夹下。该插件主要作用:
使用 HtmlWebpackPlugin:
plugin>npm install html-webpack-plugin --save-dev
此时打包产生的项目中,dist 文件夹下会产生 index.html 文件:
<html>
<head>
<meta charset="UTF-8" />
<title>Webpack Apptitle>
head>
<body>
<script type="text/javascript" src="dist/bundle.js">script>
body>
html>
但是该 index.html 还有一些问题:
dist/
前缀解决:
publicPath
const path = require("path");
const webpack = require('webpack');
const htmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...省略其它
plugins: [
new webpack.BannerPlugin('版权信息'),
new htmlWebpackPlugin({
template: 'index.html'
})
]
}
项目发布之前需要对 JS 文件进行压缩,避免 JS 文件过大。
install [email protected] --save-dev
webpack.config.js
中配置 uglifyjs 插件此时再去查看 bundle.js 的代码,可以看到该文件已经被压缩过了。
webpack 提供了一个可选的本地开发服务器,这个本地服务器基于 node.js 搭建,内部使用 express 框架,可以实现浏览器自动刷新修改后的结果。
module.exports = {
// ...省略其它
devServer: {
// 为哪一个文件夹提供本地服务
contentBase: './dist',
// 页面实时刷新
inline: true
// port
port: 8080
}
}
"dev": "webpack-dev-server --open"
之前所有的Vue配置文件都放在webpack-config.js
中,但在实际开放中,对于该文件中的配置应该根据不同的环境进行配置,例如:UglifyjsWebpackPlugin插件应该在生产环境中使用,开放环境应该禁用方便调试;webpack-dev-server应该在开发环境中使用,提高开发效率,在生产环境中禁用。
为此,可以将webpack-config.js配置文件进行分离,分离出三个文件:
具体实现步骤:
// ----------------prod.config.js
const uglifyWebpackPlugin = require('uglifyjs-webpack-plugin');
const webpackMerge = require('webpack-merge');
const baseConfig = require('./base.config');
// 将生产环境和base环境进行导出
module.exports = webpackMerge(baseConfig, {
plugins: [
new uglifyWebpackPlugin()
]
})
// ----------------dev.config.js
const baseConfig = require('./base.config');
const webpackMerge = require('webpack-merge');
// 将开发环境和base环境进行导出
module.exports = webpackMerge(baseConfig, {
devServer: {
contentBase: './dist',
inline: true
}
})
"build": "webpack --config ./build/prod.config.js",
"dev": "webpack-dev-server --open --config ./build/dev.config.js"
module.exports = {
entry: "./src/main.js",
// 出口需要以path和filename形式存在
output: {
// 修改./dist ---> ../dist
path: path.resolve(__dirname, '../dist'),
filename: 'bundle.js',
// publicPath: 'dist/'
}
// ...这里省略
}