前言
相信很多小伙伴在平时开发vue项目时都使用的vue cli脚手架,该脚手架已经帮忙配置好了很多基础功能,不再需要我们亲自去配置,省去了很多配置时间,但如果我们自己手动去搭建一个这样的框架,该如何搭建呢?本文主要就是介绍一下如何动手搭建一个vue2开发环境,并做一些相应的打包优化。
1、搭建webpack开发环境
新建一个项目文件夹,然后npm init,添加package.json文件,接着安装webpack4和相应的webpack-dev-server开发服务器:
yarn add -D webpack webpack-cli // 我采用的webpack@4.46.0 webpack-cli@4.9.2
yarn add -D webpack-dev-server // webpack-dev-server@4.8.1
在src目录下添加入口文件main.js,并在build目录下新建webpack.config.js打包配置文件,并做如下配置:
// webpack.config.js
const path = require('path')
module.exports = {
entry: path.resolve(__dirname, '../src/main.js'),
output: {
path: path.resolve(__dirname, '../dist'),
// 生成的 js 文件名称
filename: 'app.js'
},
module: {
//
}
}
添加运行脚本即可进行基本的js打包操作:
"scripts": {
"dev": "webpack --mode development --config build/webpack.config.js"
}
2、配置babel
在对js文件进行打包时,我们还需要转换一些es6语法,有些浏览器对新的es语法不是很支持,这时候就需要利用babel对源码进行转换操作。 安装babel-loader、@babel/core(babel核心功能模块):
yarn add -D babel-loader@8.2.4 @babel/core@7.17.8 @babel/preset-env@7.16.11
安装完以后,就可以配置babel-loader来转换js语法为浏览器通用es5语法,但对于新的api语法还是不能转换,需要单独引入polyfill。
好在@babel/preset-env提供了一个useBuiltIns参数,当将其值设为usage时,就可以按需加载polyfill。在设置该值的同时,还需要安装core-js@3,支持最新语法。
yarn add core-js
安装完依赖以后在根目录添加配置文件babel.config.js,并配置相应的webpack.config.js:
// babel.config.js
module.exports = {
exclude: [
/node_modules/
],
presets: [
[
// 转换es6等新特性代码
'@babel/preset-env',
{
useBuiltIns: "usage", // 按需引入ployfill
corejs: 3
}
]
],
plugins: [
//
]
}
// webpack.config.js
module.exports = {
entry: path.resolve(__dirname, '../src/main.js'),
output: {
path: path.resolve(__dirname, '../dist'),
// 生成的 js 文件名称
filename: 'app.js'
},
module: {
rules: [
{
test: /\.js$/,
loader: "babel-loader"
}
]
}
}
3、配置vue loader
安装vue和识别vue单文件组件的webpack loader、模板编译器:
yarn add -D vue-loader@15.9.8 vue-template-compiler@2.6.14
yarn add vue@2.6.14
并做如下配置:
// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, '../dist'),
// 生成的 js 文件名称
filename: 'app.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
resolve: {
//引入路径不用写后缀名
extensions: ['.js', '.vue', '.json'],
//缩写扩展
alias: {
//用@直接指引到src目录下
'@': path.resolve(__dirname, './src')
}
},
plugins: [
new VueLoaderPlugin()
]
}
// main.js
import Vue from 'vue'
import App from './App'
new Vue({
render: h => h(App)
}).$mount('#app')
4、添加vue-router和vuex
yarn add vue-router@3.5.3 vuex@3.6.2
在src目录下添加router和store目录,并作如下配置:
// route/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.NODE_ENV === 'production' ? '/webpack-vue/' : '/', // 根据部署路径进行设置
routes: [
{
path: '/',
name: 'HelloVue',
component: () =>
import(/* webpackChunkName: "HelloVue" */ '../views/HelloWorld') // webpack魔法注释设置chunkName,代码分割,按需引入
},
{
path: '/test',
name: 'Test',
component: () => import(/* webpackChunkName: "Test" */ '../views/test')
}
]
})
//store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import app from './app'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
app
}
})
// main.js
import router from './router'
import store from './store'
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
5、配置html-webpack-plugin
当代码打包完成后可以自动生成html文件,或者以现有的html文件为模板生成一个,同时将各种类型bundle嵌入html当中,只需在webpack配置中添加该插件即可:
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports= {
...
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html')
})
]
}
该插件还能设置一些变量,在模板index.html中使用:
module.exports= {
...
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html'),
testVal: 'testetsttest'
})
]
}
//public/index.html
<body>
<%= htmlWebpackPlugin.options.testVal %>
<div id="app">
</div>
</body>
6、sass
安装sass-loader和node-sass预处理样式:
yarn add -D sass-loader@7.2.0 node-sass@4.14.1
除此之外还要安装css-loader和style-loader,css-loader解析css文件,style-loader主要是将css解析到html页面的style上:
yarn add -D css-loader@2.0 style-loader@2.0.0
7、postcss-loader
安装postcss-loader和autoprefixer实现自动添加css3前缀:
yarn add -D postcss-loader@4.3.0 autoprefixer@8.6.5
添加postcss.config.js,在package.json文件中添加browserslist,配置适配的浏览器范围,也可以在插件对象中添加browserslist属性:
// postcss.config.js
module.exports = {
plugins: {
autoprefixer: {
// browsers: [
// 'last 10 Chrome versions',
// 'last 5 Firefox versions',
// 'Safari >= 6',
// 'ie > 8'
// ]
}
}
}
"browserslist": [
"> 1%",
"last 2 versions",
"Safari >= 6",
"ie > 8"
]
第6步和第7步的相关配置文件如下:
module.exports= {
modules: {
rules: [
{
test: /\.(scss|sass|css)$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2 // 如果css中使用@import语句,就先采用前面两个loader处理@import模块
}
},
'postcss-loader',
'sass-loader'
]
}
]
}
}
8、devServer
配置webpack开发服务器,具体配置如下:
module.exports = {
devServer: {
hot: true, // 开启热更新
port: 8000, // 端口号
historyApiFallback: true, // 针对vue路由模式为history时,请求不同路径返回html
open: true // 构建完自动打开浏览器
},
devtool: 'eval-cheap-module-source-map' // 开启source-map
}
9、配置webpack打包图片、文字等文件
添加webpack file-loader或url-loader,file-loader可以解析文件url,url-loader有file-loader的能力,同时可以把小于一定大小的文件转换成base64,内联到代码中,安装配置如下:
yarn add -D url-loader@4.1.1
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?j|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
esModule: false,
name: process.env.NODE_ENV === 'production' ? 'img/[name].[hash].[ext]' : 'img/[name].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: "url-loader",
options: {
limit: 10000,
name: process.env.NODE_ENV === 'production' ? 'fonts/[name].[hash].[ext]' : 'fonts/[name].[ext]'
}
}
]
}
}
10、配置环境变量
不同的环境变量可以使用不同的打包文件,比如开发环境使用快速方便的打包配置文件,不需要压缩代码、需要热更新、不需要抽离css文件、开启source-map等等,而生产环境需要压缩代码、提取css文件,打包使用chunkhash和contenthash等。 可以通过安装cross-env来跨平台设置环境变量:
yarn add -D cross-env
//添加npm script运行不同的脚本
"dev": "cross-env NODE_ENV=development webpack-dev-server --mode development --config build/webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --mode production --config build/webpack.config.js"
在项目根目录的build文件夹中分别添加webpack.base.config.js、webpack.dev.config.js、webpack.prod.config.js、webpack.config.js。webpack.base.config.js当中维护公共的打包配置,dev和prod分别维护开发和生产的打包配置,webpack.config.js用来根据不同环境变量合并选择相应的打包配置。
const { merge } = require('webpack-merge') // 合并打包配置
const baseConfig = require('./webpack.base.config')
const devConfig = require('./webpack.dev.config')
const prodConfig = require('./webpack.prod.config')
module.exports = (env, argv) => {
console.log(argv)
const config = process.env.NODE_ENV == 'development' ? devConfig : prodConfig
return merge(baseConfig, config)
}
11、hash、chunkhash、contenthash
hash一般是结合CDN缓存来使用,通过webpack构建之后,生成对应文件名自动带上对应的MD5值。如果文件内容改变的话,那么对应文件哈希值也会改变,对应的HTML引用的URL地址也会改变,触发CDN服务器从源服务器上拉取对应数据,进而更新本地缓存。
hash是跟整个项目的构建打包有关,只要项目中某个文件有改动,整个项目构建的hash值都会改变。
chunkhash和hash不太一样,它根据入口文件解析、构建对应的chunk,生成对应的哈希值。我们在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响。
如果都使用chunkhash的话,css文件单独抽离时,如果改动了引入这个css文件的js文件后,css文件使用chunkhash也会跟着改动,这其实并不是我们想要的,这时候可以针对css文件使用contenthash,contenthash和内容相关,内容不变就不会改变。
module.exports = {
entry: path.resolve(__dirname, '../src/main.js'),
output: {
path: path.resolve(__dirname, '../dist'),
// 生成的 js 文件名称
filename: process.env.NODE_ENV == 'production' ? 'app.[chunkhash:8].js' : 'app.js',
// 生成的 chunk 名称
chunkFilename: process.env.NODE_ENV == 'production' ? '[name]/[name].[chunkhash:8].js' : '[name]/[name].js'
}
}
12、css样式压缩和抽离
配置mini-css-extract-plugin插件抽离css样式,配置optimize-css-assets-webpack-plugin插件进行css压缩。 安装mini-css-extract-plugin:
yarn add -D mini-css-extract-plugin@1.6.2
安装optimize-css-assets-webpack-plugin,同时还需要搭配cssnano使用:
yarn add -D optimize-css-assets-webpack-plugin@4.0.3 cssnano@4.1.7
webpack配置如下:
const MiniCssExtractPlugin = require("mini-css-extract-plugin") // 抽离css到单独文件中
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') // css压缩
module.exports = {
module: {
rules: [
{
test: /\.(scss|sass|css)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'postcss-loader',
'sass-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css'
}),
new OptimizeCSSPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require('cssnano')
})
]
}
13、定义全局打包可替换变量
利用webpack DefinePlugin可以定义全局的打包可替换变量,在打包处理时webpack会自动识别该变量,具体配置如下:
const CopyWebpackPlugin = require('copy-webpack-plugin')
const webpack = require('webpack')
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
testVal: 'testetsttest'
}),
new VueLoaderPlugin(),
new CopyWebpackPlugin([{ // 将public文件目录直接copy到打包最终文件中
from: 'public'
}]),
// 定义全局打包可替换变量
new webpack.DefinePlugin({
BASE_URL: process.env.NODE_ENV === 'production' ? JSON.stringify('https://youchen133.github.io/webpack-vue/') : JSON.stringify('/'),
SOMETHING: JSON.stringify('this is something!')
})
]
}
定义的变量可以直接在html中使用:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no">
<title>vue cli框架实现</title>
<script src="<%= BASE_URL %>lib/vue.runtime.min.js"></script>
<script src="<%= BASE_URL %>lib/vue-router.min.js"></script>
<script src="<%= BASE_URL %>lib/vuex.min.js"></script>
</head>
<body>
111
<%= htmlWebpackPlugin.options.testVal %>
<%= SOMETHING %>
<div id="app">
</div>
</body>
14、webpack优化之externals
当我们打包时,vue、vuex和vue-router等基础库也会打包到最终的bundle文件当中,这会导致整个的bundle体积比较大,影响加载时间,而且也影响打包速度,其实可以采用直接在html引入cdn链接的方式解决这个问题,这时候就需要配置webpack externals,使得基础库不参与打包。
在public文件夹目录下添加基础库官方最终打包好的生产版本vuex.min.js、vue.runtime.min.js、vue-router.min.js,可以直接在html中引入,然后配置webpack externals属性:
module.exports = {
// 排除文件引入打包,直接在html中引入
externals: {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter'
}
}
15、配置eslint和prettier
在开发过程中配置eslint和prettier统一代码风格,具体安装步骤如下:
安装eslint-loader、eslint:
yarn add -D eslint-loader@4.0.2 eslint@7.32.0
添加vue-eslint-parser解析vue文件,同时安装@babel/eslint-parser,配合vue-eslint-parser,解析一些最新的es6语法:
yarn add -D @babel/eslint-parser@7.17.0 vue-eslint-parser@8.3.0
安装eslint-plugin-vue、eslint-plugin-prettier、eslint-config-prettier插件:
yarn add -D eslint-plugin-vue@7.20.0 eslint-plugin-prettier@4.0.0 eslint-config-prettier@8.5.0
eslint-plugin-vue是对.vue 文件进行代码校验的插件,同时扩展了一些vue解析语法。 eslint-plugin-prettier先调用prettier对你的代码进行格式化,然后会把格式化前后不一致的地方进行标记,通过配置 'prettier/prettier': 'error' 此条规则会将标记地方进行 error 级别的报错提示。为了防止冲突,可以配置eslint-config-prettier。 项目根目录添加.eslintrc.js和.prettierrc.js文件:
// .eslintrc
module.exports = {
root: true,
env: {
browser: true,
es6: true,
node: true
},
globals: {
SOMETHING: true // 防止全局打包替换变量eslint检测报错
},
parser: 'vue-eslint-parser', // 识别vue文件
parserOptions: {
sourceType: 'module',
parser: '@babel/eslint-parser' // 配合vue-eslint-parser,解析一些最新的es6语法
},
extends: [
'eslint:recommended',
'plugin:vue/essential',
'prettier'
],
plugins: [
'vue',
'prettier'
],
rules: {
'prettier/prettier': 'error',
//强制使用单引号
quotes: ['error', 'single'],
//强制不使用分号结尾
semi: ['error', 'never']
}
}
//.prettierrc
module.exports = {
printWidth: 80,
tabWidth: 2,
useTabs: true,
semi: false,
singleQuote: true,
...
}
16、总结
以上就是本次文章的全部内容,还有其它的一些webpack打包构建优化方法,大家也可以去查找相关资料并进行实际尝试一下,本次的项目示例链接为github.com/youchen133/…
参考文章: