最近感觉自己越来越像一个API调用程序员,很多基础的原理以及项目构建都没实际操作过,所以这里动手自己去搭建了一个vue项目,从webpack配置到vue配置,以及构建的优化,虽然写得并不好,但是自己在这个过程中也学到了一些东西,以此记录。
由于是真的从零开始,所以长文预警!!!😂
初始化项目
首先☝️,在命令行中创建文件夹并进入,使用npm命令初始化项目:
mkdir vue-starter && cd vue-starter
npm init
然后,创建index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue Starter</title>
</head>
<body>
<h1>First Step!</h1>
</body>
</html>
创建src文件夹,并在src文件中创建main.js文件:
mkdir src && cd src
touch main.js
src/main.js中写入:
window.onload = () => {
console.log('load');
};
这个时候如果在index.html中引入./src/main.js,并在浏览器中运行index.html会发现控制台中打印了‘load’。代码少看起来并不复杂,但是当我们业务变复杂,之后代码量过大,就需要我们进行打包构建了😊。
所以下面进行webpack配置。
webpack第一步
首先☝️,安装webpack 和webpack-cli:
npm i -D webpack webpack-cli
然后,在package.json中配置运行webpack的脚本命令:
"scripts": {
"build": "cross-env NODE_ENV=production webpack --mode=production --config webpack.config.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
这里用了新依赖,cross-env,进行安装:
npm i -D cross-env
关于build脚本命令解释:
- cross-env依赖用于跨平台设置环境变量。在多数情况下,在windows平台下使用类似于: NODE_ENV=production的命令行指令会卡住;windows平台与POSIX在使用命令行时有许多区别(POSIX,使用$ENV_VAR;windows使用%ENV_VAR%)。cross-env就是解决这类跨平台问题的,统一每个平台的命令。
- NODE_ENV=development 设置 NODE 的环境变量为开发环境
- --mode=procution配置为生产环境模式
- --config webpack.config.js 指明配置文件位置(相对路径)
创建webpack.config.js,webpack配置如下:
const path = require('path');
const config = {
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist/')
}
};
module.exports = config;
上面是最基本的webpack配置,大概意思就是根据entry的./src/main.js内容进行打包构建输出到bundle.js.
然后运行npm run build命令会,会得到dist文件夹以及dist/bundle.js文件,dist/bundle.js就是src/main.js打包构建之后的内容。
现在将index.html中的src/main.js改成dist/bundle.js。
<script src="./dist/bundle.js"></script>
再在浏览器中打开index.html,可以看到也得到了相同的效果。
生成的bundle文件添加hash
为什么添加hash?
是为了防止浏览器缓存机制阻止文件的更新,为打包文件添加hash后缀之后,每次构建打包生成的文件名的hash都会发生改变,强制浏览器进行刷新,获取当前最新的文件就可以防止使用缓存文件。
如何添加?
在output中设置:
output: {
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, './dist/'),
}
设置好hash之后,运行npm run build命令会发现dist下生成的bundle带上了hash。
自动生成html文件
在npm run build之后,dist文件夹中并没有index.html文件,要想引用打包的文件,需要手动引用,并且由于上一步为bundle添加了hash,所以每次构建都需要手动修改script标签的src路径。
使用HtmlWebpackPlugin可以自动生成html文件并注入打包的文件。
安装包:
npm i -D html-webpack-plugin
在webpack.config.js中引入并在plugins中添加配置:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const htmlPlugin = new HtmlWebpackPlugin({
// 生成的html的title
title: 'Vue Starter',
// 生成的html的文件名
filename: 'index.html',
// 注入bundle到body中
inject: 'body'
});
const config = {
entry: './src/main.js',
output: {
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, './dist/')
},
plugins: [
htmlPlugin
]
};
module.exports = config;
现在运行npm run build可以看到生成了index.html并且自动引用了带hash后缀的bundle.[hash].js。
引入vue
首先,安装vue与vue-loader:
npm i -D vue vue-loader
安装成功之后会看到控制台有一个warning❗️:
npm WARN vue-loader@15.6.2 requires a peer of css-loader@* but none is installed. You must install peer dependencies yourself.
意思是安装的包还需要依赖css-loader,所以继续安装css-loader:
npm i -D css-loader
然后在webpack.config.js中添加vue相关的loader:
module: {
rules: [
{
test: /\.vue$/,
loader:'vue-loader'
}
]
}
src下创建App.vue:
<template>
<div>
Second Step!
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
src下创建main.js:
import Vue from 'vue';
import App from './App.vue';
const root = document.createElement('div')
document.body.appendChild(root)
new Vue({
render: (h) => h(App)
}).$mount(root)
这时候我们运行npm run build命令会得到以下报错❗️:
Module Error (from ./node_modules/vue-loader/lib/index.js):
[vue-loader] vue-template-compiler must be installed as a peer dependency, or a compatible compiler implementation must be passed via options.
提示vue-loader需要依赖vue-template-compiler包,安装该包即可:
npm i -D vue-template-compiler
然后运行npm run build又报错❗️啦:
ERROR in ./src/App.vue
Module Error (from ./node_modules/vue-loader/lib/index.js):
vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.
@ ./src/main.js 2:0-28 8:21-24
ERROR in ./src/App.vue?vue&type=template&id=4fa9bc52& 2:0
Module parse failed: Unexpected token (2:0)
You may need an appropriate loader to handle this file type.
这个bug是因为vue-loader版本问题引起的,v15版本需要依赖VueloaderPlugin包,解决这个问题的办法是使用v14版本的vue-loader,在package.json中修改vue-loader的版本为14.2.2
,然后npm install一下就可以了。具体的解决办法参考👉enableVueLoader does not include VueLoaderPlugin?
然后再运行npm run build,可以看到生成了dist文件夹与dist文件夹下的bundle.js。
然后在浏览器中运行index.html可以看到,页面中内容为“Second Step!”,表示我们的npm run build成功构建了我们的vue代码啦✌️。
webpack开发环境(npm run dev)配置
但是,这样的开发方式还不是很方便,每次写完代码之后,需要手动npm run build构建打包,再手动刷新页面。所以我们需要配置开发环境的运行脚本。
首先,在package.json中添加dev命令脚本:
"scripts": {
"build": "cross-env NODE_ENV=production --mode=production webpack --config webpack.config.js",
"dev": "cross-env NODE_ENV=development webpack-dev-server --mode=development --config webpack.config.js --open",
"test": "echo \"Error: no test specified\" && exit 1"
}
关于dev脚本命令解释:
- 与build不同的是,dev脚本使用了webpack-dev-server,webpack-dev-server是一个小型的Node.js Express服务器,它使用webpack-dev-middleware来服务于webpack的包,用于开发者在开发中配置使用。
- --mode=development设置模式为开发环境
- --open设置后,会在服务器启动之后立即打开页面
关于webpack-dev-server的理解与使用,可以参考这篇文章👉详解webpack-dev-server的使用。
webpack-dev-server是独立的包,所以进行安装:
npm i -D webpack-dev-server
对App.vue进行修改,将Second Step!修改为Third Step!
然后命令行中运行npm run dev,服务器启动成功,命令行中出现:Project is running at http://localhost:8080/,从浏览器中进入http://localhost:8080/访问页面,但是页面中的内容依旧是Second Step!。
服务器运行成功,但是页面内容未更新,这是什么原因呢?
检查根目录下的index.html看到我们引入js的路径为/dist/bundle.js,但是进行npm run dev命令并没有更新dist的bundle.js。
这是因为webpack-dev-server打包的内容是放在内存中的,并不会在真实的目录中生成。
这里只需要把index.html中的引入bundle.js的script标签删除即可。因为在前面加了html-webpack-plugin包,在运行过程中会自动对内存中的index.html插入js。所以不需要再手动插入。
注意:
如果还未使用html-webpack-plugin,则需要用publicPath来解决。设置devServer的publicPath为/dist/即可。
关于publicPath的理解可以参考这里👉Webpack中publicPath详解
引入webpack-dev-server的目的就是为了在开发阶段根据修改快速更新页面,先试一下效果。修改App.vue内容为Third Step Updated!,然后Ctrl + s保存看看页面是否更新,在控制台中可以看到这样的提示:
[WDS] App updated. Recompiling...
bundle.js:7 [WDS] App hot update...
可以看到进行了重新编译和更新,页面内容也进行了刷新,不需要重新运行npm run dev。
进阶配置
自动清理dist文件夹
前面添加了hash的设置,每次npm run build的时候都会生成新的hash后缀的文件,不会覆盖之前的bundle.[hash].js,导致dist文件夹的内容越来越多。
这里就可以使用clean-webpack-plugin包实现每次构建的时候自动清理dist文件夹,首先安装clean-webpack-plugin包:
npm i -D clean-webpack-plugin
webpack.config.js中引入clean-webpack-plugin包并在plugins中配置:
const CleanWebpackPlugin = require('clean-webpack-plugin');
if (process.env.NODE_ENV === 'production') {
config.plugins.push(new CleanWebpackPlugin());
}
配置之后,每次运行npm run build就可以清理dist文件夹之前的内容了。
添加css-loader
一开始就安装了css-loader没使用,就算在App.vue中添加了样式也不会出错,那么css-loader到底是干什么的呢?
webpack的官方解释:
css-loader 解释(interpret) @import 和 url() ,会 import/require() 后再解析(resolve)它们。
在某些情况下,可能需要在js中引用css文件,例如添加一些全局的样式配置或者是通过引入css文件达到css修改热更新的目的等。这时候需要在js中通过require('xxx.css')引入,但是运行项目会出现以下错误。
ERROR in ./src/text.css 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
这时候在 webpack.config.js 中添加 css-loader 就能解决这个问题:
{
test: /\.css$/,
loader:'css-loader'
}
所以 css-loader 是处理 css 文件,将 css 装载到 javascript。
注意
安装最新版本的css-loader(2.1.0)在构建(npm run build)的时候会出现如下错误:
ValidationError: CSS Loader Invalid Options
options should NOT have additional properties
由于目前暂未找到解决方法,所以暂时安装指定的旧版本(1.0.1),等找到解决方法之后会更新。
关于css-loader理解参考这里👉你真的知道 css-loader 怎么用吗?
关于style-loader理解参考这里👉style-loader详细使用说明
关于样式相关的loader对比可以参考这里👉style-loader、css-loader、mini-css-extract-plugin 区别
添加图片处理loader
<template>
<div>
Third Step!
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
div {
width: 200px;
height: 200px;
background: url("./logo.png");
}
</style>
其中的关键是background的url设置,运行npm run dev会发现报错❗️:
ERROR in ./src/logo.png 1:0
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type.
这个问题是项目不能识别图片后缀的原因,所以添加引用资源的loader:
npm i -D url-loader
webpack.config.js配置图片相关的loader:
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader'
}
然后项目就可以成功运行且引入图片了✅。
引入less
安装less和less-loader:
npm i -D less less-loader
webpack.config.js中添加less的loader配置:
{
test: /\.less$/,
loader: 'style!css!less'
}
然后既可以使用less了✌️!
提取css
extract-text-webpack-plugin只支持 webpack 4 以下提取 CSS 文件,webpack 4使用extract-text-webpack-plugin包的alpha版本,安装:
npm i -D extract-text-webpack-plugin@next
在webpack.config.js中首先对vue文件中的样式做etract处理, 添加extractCSS的配置:
{
test: /\.vue$/,
loader:'vue-loader',
options: {
extractCSS: true
}
}
然后在plugins中使用extract-text-webpack-plugin:
const ExtractTextPlugin = require('extract-text-webpack-plugin');
plugins: [
htmlPlugin,
new ExtractTextPlugin('style.css')
]
运行npm run build就可以成功单独提取css了。
关于npm安装@next的解释参考这里👉npm使用小技巧
自动解析确定的扩展
当前配置中,引用.vue等后缀的文件,不能省略后缀,必须明确写出,否则,无法识别。
可以使用resolve的extensions配置,在webpack.config.js中添加下面的配置即可。
// other configurations
resolve: {
extensions: ['*', '.js', '.vue', '.json']
}
// other configurations
现在就可以愉快的引入文件且不添加后缀啦。
分离生产环境和开发环境的webpack配置,使用webpack-merge合并通用配置
这一部分官网具体有说:https://webpack.docschina.org/guides/production/
在上面的配置中,使用 npm run dev 运行开发环境,npm run build 运行生产环境。开发环境和生产环境的配置都在 webpack.config.js 中,对于两种环境不同的配置,使用 if 逻辑进行了判断与单独配置。当配置逻辑逐渐增加,if 中的逻辑会逐渐臃肿,所以有必要对生产环境和开发环境的配置进行分离。
创建webpack.config.dev.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const htmlPlugin = new HtmlWebpackPlugin({
// 生成的html的title
title: 'Vue Starter',
// 生成的html的文件名
filename: 'index.html',
// 注入bundle到body中
inject: 'body'
});
const config = {
entry: './src/main.js',
output: {
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, './dist/')
},
module: {
rules: [
{
test: /\.vue$/,
loader:'vue-loader',
options: {
extractCSS: true
}
},
{
test: /\.css$/,
loader:'css-loader'
},
{
test: /\.less$/,
loader: 'style!css!less'
},
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader'
}
]
},
plugins: [
htmlPlugin,
new ExtractTextPlugin('style.[hash].css')
],
resolve: {
extensions: ['*', '.js', '.vue', '.json']
},
devtool: false,
devServer: {
noInfo: true
}
};
module.exports = config;
去掉了生产环境判断添加配置的逻辑。
创建webpack.config.pro.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const htmlPlugin = new HtmlWebpackPlugin({
// 生成的html的title
title: 'Vue Starter',
// 生成的html的文件名
filename: 'index.html',
// 注入bundle到body中
inject: 'body'
});
const config = {
entry: './src/main.js',
output: {
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, './dist/')
},
module: {
rules: [
{
test: /\.vue$/,
loader:'vue-loader',
options: {
extractCSS: true
}
},
{
test: /\.css$/,
loader:'css-loader'
},
{
test: /\.less$/,
loader: 'style!css!less'
},
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader'
}
]
},
plugins: [
htmlPlugin,
new ExtractTextPlugin('style.[hash].css'),
new CleanWebpackPlugin()
],
resolve: {
extensions: ['*', '.js', '.vue', '.json']
},
devtool: '#source-map',
devServer: {
noInfo: true
}
};
module.exports = config;
将 if 逻辑删除,直接配置生产环境需要的 plugins。
修改 package.json 中开发环境和生产环境的配置文件:
"dev": "cross-env NODE_ENV=development webpack-dev-server --mode=development --config webpack.config.dev.js --open",
"build": "cross-env NODE_ENV=production webpack --mode=production --config webpack.config.pro.js",
另外,除了一些特殊配置,可以看到还有很多相同的重复配置,本着 DRY 原则,可以提取通用的配置,然后使用 webpack-merge 进行合并。
首先安装 webpack-merge:
npm i -D webpack-merge
然后,将之前的 webpack.config.js 改名为 webpack.common.js,修改代码为 生产环境和开发环境通用的配置,主要是通用的 enter、output、module 和通用的 plugins。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const htmlPlugin = new HtmlWebpackPlugin({
// 生成的html的title
title: 'Vue Starter',
// 生成的html的文件名
filename: 'index.html',
// 注入bundle到body中
inject: 'body'
});
const config = {
entry: './src/main.js',
output: {
filename: 'bundle.[hash].js',
path: path.resolve(__dirname, './dist/')
},
module: {
rules: [
{
test: /\.vue$/,
loader:'vue-loader',
options: {
extractCSS: true
}
},
{
test: /\.css$/,
loader:'css-loader'
},
{
test: /\.less$/,
loader: 'style!css!less'
},
{
test: /\.(png|jpg|gif)$/,
loader: 'url-loader'
}
]
},
plugins: [
htmlPlugin,
new ExtractTextPlugin('style.[hash].css')
],
resolve: {
extensions: ['*', '.js', '.vue', '.json']
},
};
module.exports = config;
现在可以在 生产环境和开发环境的配置文件中使用 webpack-merge 和 通用的 common 配置。
webpack.config.dev.js:
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: '#eval-source-map',
devServer: {
noInfo: true,
open: true
}
});
webpack.config.pro.js:
const merge = require('webpack-merge');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
plugins: [
new CleanWebpackPlugin()
],
devtool: '#source-map',
});
然后修改 package.json,目的在于将之前的 mode 设置直接放到配置文件中,这样可以集中处理生产环境和开发环境的区别配置。
"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.dev.js",
"build": "cross-env NODE_ENV=production webpack --config webpack.config.pro.js",
这样就完成了,生产环境与开发环境的配置分离啦~✌️
关于 source map
首先什么是 source map?
一句话来说,source map 就是一个信息文件,用来记录打包文件转换前的位置信息,便于调试。
详情参考阮一峰大大的博文:JavaScript Source Map 详解
关于 webpack 中如何配置 source-map?
添加eslint
使用eslint可以保证保证编码规范,自动检验代码,安装:
npm i -D eslint eslint-config-standard babel-eslint eslint-config-vue eslint-plugin-vue eslint-plugin-standard eslint-plugin-promise eslint-plugin-import
然后使用命令初始化eslint,会生成.eslintrc.js:
eslint --init
在.eslintrc.js 写入如下配置:
module.exports = {
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
},
extends: ['vue', 'standard', 'plugin:vue/recommended'],
plugins: [
'vue',
"standard",
"promise"
],
'rules': {
"indent": ["error", 4, { "SwitchCase": 1 }],
'arrow-parens': 0,
'generator-star-spacing': 0,
"semi": ["error", "always", { "omitLastInOneLineBlock": true }],
"no-lone-blocks": "error",
"no-multi-spaces": "error",
"no-multiple-empty-lines": ["error", { "max": 2 }],
"no-param-reassign": "warn",
"no-spaced-func": "error",
"no-use-before-define": "error",
"no-unused-vars": "error",
"no-with": "error",
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }],
"comma-spacing": ["error", { "before": false, "after": true }],
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
}
然后在 package.json 中添加 lint 脚本命令:
"lint": "eslint --ext .js,.vue src"
就可以通过npm run lint进行检查。
为了能在编写代码的过程中编辑器就提醒错误,需要在webstorm -> Preferences -> Languages & Frameworks -> Javascript -> Code Quality Tools -> ESLint中勾选enable即可。
参考
本文相关的git地址:vue-starter