在使用Vue开发单页面的时候,咱们大多数时候都是使用的官方CLI工具,如今的Vue CLI已经迭代到了4.X了,能够说很成熟稳定了,能知足大多数要求,并且上手简单。本着折腾摸索的精神,仍是打算本身搭建一个开发环境,熟悉各个流程。css
本文不涉及Webpack和babel知识的讲解,建议了解一下Webpack的基本知识来看这篇文章会更好理解。这个在网上能找到不少教程。关于node.js和npm安装在这里也再也不赘述,相信作前端开发这个是电脑必备的。html
若是你在搭建过程当中遇到不明的报错,查看报错信息并记录排查,有时候相关的插件在更新事后使用方式会发生变化,有可能你按着个人配置走下来也报错,那么看看官方文档(通常在github上查相关的仓库便可)有没有改变写法,好比此次我配置的clean-webpack-plugin插件,之前的版本是不须要解构的,可是如今必须解构了,否则它会报错并提示不是构造函数前端
大佬绕路轻喷。。。vue
更新:若是开发环境和生产环境都使用插件把CSS分离出来成为单独的文件,那么你在开发过程当中会发现热更新对CSS不生效,因此解决方法就是分离CSS仅配置在生产环境。给你们埋了个坑,sorry!node
优化篇已经出炉:基于Webpack4的Vue移动端开发环境-优化篇webpack
个人node.js及npm版本以下:css3
node -v
v12.13.0
npm -v
v6.12.0
复制代码
首先你要新建一个文件夹,我这儿叫 customized-vue-proj-mobile
,而后在你的文件夹右键打开git bash输入如下命令来初始化项目(须要安装git,固然用cmd也是能够的):git
npm init
复制代码
执行完这个命令事后,会在目录生成一个 package.json
的文件,个人文件内容以下:es6
{
"name": "customized-vue-proj-mobile",
"version": "1.0.0",
"description": "customized vue development environment",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/cookiepool/customized-vue-proj-mobile.git"
},
"keywords": [
"vue",
"mobile"
],
"author": "LEE",
"license": "MIT",
"bugs": {
"url": "https://github.com/cookiepool/customized-vue-proj-mobile/issues"
},
"homepage": "https://github.com/cookiepool/customized-vue-proj-mobile#readme"
}
复制代码
这儿关于npm包管理也有不少知识点,这里不在展开,建议你们能够自行了解下github
这样一个项目就先初始化完毕,接下来开始进入各类工具的安装来完成环境的搭建。
基于webpack的话确定要先安装好webpack才能继续安装后续的,因此先来安装命令:
npm install webpack --save-dev
复制代码
--save-dev
表示将包安装信息放入到package.json
的devDependencies
里面,这个不会用于生产环境,-D
等效于--save-dev
。--save
表示将包安装信息放入到package.json
的dependencies
里面,这个会打包用于生产环境。-S
等效于--save
。
接下来还须要安装CLI,从webpack4开始必需要安装webpack cli才能执行webpack相关的命令
npm install webpack-cli -D
复制代码
接下来输入 npx webpack --help
来测试webpack是否处于可用状态,若是输入这个命令后面板出现一大串配置帮助信息则表明webpack可用。
npx 能够直接调用项目内部安装的模块,而不须要全局安装npm模块,若是上面的命令你不使用npx你会发现系统显示
bash: webpack: command not found
。
如今在项目目录下新建一个src文件夹,里面新建一个main.js文件,在里面先随便写点js代码。而后在创建一个build文件夹,并新建一个webpack.config.js配置文件。这样一来咱们的目录结构就是这个样子:
文件建好了可是尚未内容,确定跑不起来,接下来对webpack.config.js操做一番,这里我不叙述具体过程了(#滑稽保命),直接贴代码,里面我写了注释:
// build/webpack.config.js
// node.js里面自带的操做路径的模块
const path = require('path');
module.exports = {
// 指定模式,这儿有none production development三个参数可选
// 具体做用请查阅官方文档
mode: 'development',
// webpack打包的入口文件
entry: {
main: path.resolve(__dirname, '../src/main.js')
},
// webpack打包的输出相关的额配置
output: {
// 打包事后的文件的输出的路径
path: path.resolve(__dirname, '../dist'),
// 打包后生成的js文件,带hash值来保证文件的惟一性
filename: 'js/[name].[hash:4].js',
// 生成的chunk文件名
chunkFilename: 'js/[name].[hash:4].js',
// 资源的引用路径(这个跟你打包上线的配置有关系)
publicPath: '/'
}
}
复制代码
而后呢再把package.json改造一下,在scripts处添加一句 dev
这个命令:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack ./src/main.js --config ./build/webpack.config.js"
},
复制代码
一顿操做事后,咱们开始来试一试这个命令能不能用,在控制台下输入:
npm run dev
复制代码
稍等一下子就会出现如下信息,则表明咱们打包输出成功:
此时项目的结构变成这个样子:
环境搭建到这儿只能说webpack配置正常了,还有许多额外东西须要配置,好比如下
首先来一波安装命令,安装好相关的依赖:
npm install babel-loader @babel-core @babel/preset-env -D
复制代码
参考我这篇文章来了解一下babel,固然社区上还有许多大佬写的文章,多看几篇能够了解的更加透彻。
如今咱们须要配置webpack来使其支持babel,这里就须要使用到刚才安装的babel-loader。在配置文件中加入如下代码:
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader'
}
]
}
]
}
复制代码
在文件中加入如下内容:
// babel.config.js
module.exports = {
// 配置预置环境
presets: [
// 使用的规则
"@babel/preset-env"
]
}
复制代码
配置好后,去main.js里面写一些es6+的语法的js代码,而后执行 npm run dev
你会发现dist目录下生成的js文件语法都转换成es5的了,可是promise却没有转换,这里咱们还须要polyfill。
输入如下命令安装必须的依赖:
npm install core-js@2 -S
复制代码
安装完毕后去babel.config.js修改代码以下:
module.exports = {
// 配置预置环境
presets: [
// 使用的规则
["@babel/preset-env", {
// 这儿有false, entry, usage三个可选参数,usage能够按需引入polyfill
"useBuiltIns": "usage",
// 指定corejs版本
"corejs": 2
}]
]
}
复制代码
这样一来你的js就能够运行在低版本浏览器里面了,好比Promise。
仍是先安装依赖
npm install sass-loader dart-sass css-loader style-loader -D
复制代码
use: [{loader: 'style-loader'}, {loader: 'css-loader'}]
复制代码
由于loader加载是从右往左加载。(为何从右往左加载 )这里的编译顺序是先用css-loader将css代码编译,再交给style-loader插入到网页里面去。因此css-loader在右,style-loader在左。
下载安装好依赖事后,在配置文件 webpack.config.js
中的 module->rules
里面加入如下代码:
{
test: /\.(scss|sass)$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
}
]
}
复制代码
首先在项目目录建立一个目录public,里面再建立一个index.html,做为单页面的惟一入口。代码以下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>customized-vue-proj-mobile</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
复制代码
此时目录结构变成如图所示
建立好文件后咱们须要一个插件来处理html文件,只有js文件正常导入到html文件咱们的项目才能运行起来,输入如下命令来安装 html-webpack-plugin
:
npm install html-webpack-plugin -D
复制代码
安装完成后,再webpack配置文件的plugins中加入如下代码:
plugins: [
new htmlWebpackPlugin({
// 指定模板
template: path.resolve(__dirname, '../public/index.html'),
// 输出的文件
filename: path.resolve(__dirname, '../dist/index.html')
})
]
复制代码
开发过程确定会遇到不少媒体文件,特别是图片,webapck有专门的loader来处理这些文件,先安装好依赖:
npm install url-loader file-loader -D
复制代码
而后咱们在webpack的配置文件中加入如下代码:
{
test: /\.(jpe?g|png|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
// 当文件大于5kb时走file-loader相关的配置
limit: 5120,
// 这个参数要设置成false,否则生成图片的路径时[object Module]
esModule: false,
// 当文件大于5kb时走file-loader相关的配置
fallback: 'file-loader',
// 生成的路径和文件名
name: 'images/[name].[hash:4].[ext]'
}
}
]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 5120,
esModule: false,
fallback: 'file-loader',
name: 'media/[name].[hash:4].[ext]'
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 5120,
esModule: false,
fallback: 'file-loader',
name: 'fonts/[name].[hash:4].[ext]'
}
}
]
},
复制代码
以上咱们就配置好了媒体文件相关的处理。
安装依赖:
npm install vue-loader vue-template-compiler -D
复制代码
首先配置module里面的内容:
{
test: /\.vue$/,
use: [
{
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
}
}
}
]
}
复制代码
配置里面有个compilerOptions的参数preserveWhitespace为false,这个意思是当它的值为true时意味着编译好的渲染函数会保留全部 HTML 标签之间的空格。若是设置为 false,则标签之间的空格会被忽略。这可以略微提高一点性能可是可能会影响到内联元素的布局。具体参考此处:连接
配置好module后咱们还要引入插件,这个是必须的
// 引入vue-loader插件
const VueLoaderPlugin = require('vue-loader/lib/plugin');
// 添加到plugins中
new VueLoaderPlugin()
复制代码
具体的你们能够参考此处:连接
而后咱们还须要配置alias,这样能够减小路径过长引用的麻烦:
resolve: {
alias: {
// 写了这句,咱们能够这样写代码 import Vue from 'vue', 而且引入的是vue/dist/vue.runtime.esm.js这个版本,否则默认引入的是vue.js。这个在github的vue官方仓库dist目录下有解释。
'vue$': 'vue/dist/vue.runtime.esm.js',
// 写了这句,咱们能够这样写代码 import api from '@/api/api.js',省去处处找路径定位到src的麻烦
'@': path.resolve(__dirname, '../src')
},
// 添加一个 resolve.extensions 属性,方便咱们引入依赖或者文件的时候能够省略后缀
// 咱们在引入文件时能够这样写 import api from '@/api/api'。
extensions: ['*', '.js', '.vue']
},
复制代码
这个地方我配置了三个插件,autoprefixer、postcss-pxtorem、postcss-px-to-viewport,后两个你只须要配置其中一个便可,主要看你开发使用的rem仍是vw,自行选择便可。
npm install postcss-loader autoprefixer postcss-pxtorem postcss-px-to-viewport -D
复制代码
安装好依赖事后,在项目的根目录创建文件 postcss.config.js
,而后在文件中输入如下内容:
module.exports = {
plugins: {
// 这个工具能够实现自动添加CSS3前缀
"autoprefixer": {},
// 若是你使用rem来实现移动端多设备适配,这个工具能够把px转换为rem
/* "postcss-pxtorem": {
rootValue: 37.5, // 指定转换倍率,我如今设置这个表示1rem=37.5px;
propList: ['*'], // 属性列表,表示你要把哪些css属性的px转换成rem,这个*表示全部
minPixelValue: 1, // 须要转换的最小值,通常1px像素不转换,以上才转换
unitPrecision: 6, // 转换成rem单位的小数点后的保留位数
selectorBalckList: ['van'], // 匹配不被转换为rem的选择器
replace: true, // 替换包含rem的规则,而不是添加回退
mediaQuery: false // 容许在媒体查询中转换px
}, */
// 若是你使用vw来实现移动端多设备适配,这个工具能够把px转换为vw
"postcss-px-to-viewport": {
unitToConvert: 'px', // 把什么单位转换成vw
viewportWidth: 750, // 这个能够按照你的设计稿来设置,是750就设置750,375就设置成375
unitPrecision: 6, // 转换成vw单位的小数点后的保留位数
propList: ['*'], // 属性列表,表示你要把哪些css属性的px转换成vw,这个*表示全部
viewportUnit: 'vw', // 使用的单位,目前可选单位有vw,vh。通常咱们都有vw
fontViewportUnit: 'vw', // 字体使用的单位
selectorBlackList: [], // 匹配不被转换为vw的选择器
minPixelValue: 1, // 须要转换的最小值,通常1px像素不转换,以上才转换
mediaQuery: false, // 容许在媒体查询中转换px
replace: true, // 替换包含vw的规则,而不是添加回退
exclude: [], // 忽略一些文件,好比“node_modules”,能够是正则表达式
landscape: false, // ......
landscapeUnit: 'vw', // ......
landscapeWidth: 568 // ......
}
}
}
复制代码
而后呢,webpack配置加上:
{
test: /\.(scss|sass)$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
},
{
loader: 'postcss-loader'
}
]
}
复制代码
安装依赖:
npm install webpack-dev-server -D
复制代码
安装完成后,进行以下的配置:
// 引入webpack
const webpack = require('webpack');
// 配置devServer
devServer: {
// 默认状况不设置这个只能经过localhost:9000来访问,如今能够经过本机局域网ip来访问,
// 好比192.168.12.21:9000,手机在这个局网内也能够访问
host: '0.0.0.0',
hot: true,
port: 9200,
contentBase: './dist'
}
// 配置plugins
new webpack.NamedModulesPlugin(), // 辅助HotModuleReplacementPlugin插件
new webpack.HotModuleReplacementPlugin(), // 启用热更新必须的
复制代码
这个主要是定义这个玩意儿 process.env.NODE_ENV
,定义好这个咱们通常能够来判断什么样的环境执行什么样的代码,咱们知道webpack的打包环境和开发环境配置通常是不同的,这里不展开,后面会讲。好比咱们在入口main.js里面这样来写代码判断:
if(process.env.NODE_ENV === 'development'){
//开发环境 do something
}else if(process.env.NODE_ENV === 'production') {
//生产环境 do something
}
复制代码
那么定义这个环境怎么操做呢,第一种是借助webpack的插件DefinePlugin,
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('development')
}
}),
复制代码
那么咱们最终打包事后访问到的process.env.NODE_ENV的值就是development。
另外一种使用webpack4本身集成了的环境判断,咱们只须要在配置文件里面声明mode便可,推荐使用这种
module.exports = {
// 有none production development三个参数可选,不设置mode的话默认的process.env.NODE_ENV值为production
mode: "development",
entry: {}
.....
}
复制代码
有了上面的mode设置,咱们照样能取到process.env.NODE_ENV的值。 关于process.env.NODE_ENV的知识点这里有几篇文章能够参考:
安装依赖
npm install vue vuex vue-router -S
复制代码
安装完依赖事后,在src目录下新建一个App.vue的文件。写入如下代码:
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss">
</style>
复制代码
在src下再新建router、store两个目录,再目录下分别新建router.js和store.js。顺便再把其余文件夹也建好,如components、assets、views。如今个人目录结构以下图所示:
main.js里面修改成以下代码:
import Vue from "vue";
import App from "./App.vue";
import router from "./router.js";
new Vue({
router,
render: h => h(App)
}).$mount("#app");
复制代码
建好这些文件事后其实后面的代码书写就跟官方脚手架搭建好的写法同样,这里就不在演示其它文件的代码怎么书写了,到时能够参考个人源代码。
这儿引入vue-router和vuex事后,根据官方文档作好配置便可开始测试,我这个只测试了vue和vue-router的功能正常,vuex暂时只创建了文件,但未进行实际引入测试。后面我会把源代码提交到github供你们参考
这个时候,咱们直接npm run dev的话会打包,因此咱们须要把package.json的scripts中的dev改为以下的代码:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --config ./build/webpack.config.js"
},
复制代码
走到这步,若是你作好了前面的工做,理论上这儿执行npm run dev后就能看到效果了,我这儿的效果如图所示:
作单页面开发咱们都知道,开发环境和生产环境是不太同样的,平时使用官方cli时开发命令用的npm run dev,而打包发布时npm run build。这里面的配置确定是存在区别的。
如今在build目录下新建webpack.dev.js和webpack.prod.js两个文件,用来区分开发环境和生产环境。同时把开发环境和生产环境通用的配置都写在webpack.config.js里面。
一、webpack的mode属性值设置为development,开启这个不会压缩代码。
二、须要webpack-dev-server和热更新。
三、css不用提取到单独文件并压缩(固然你也能够提出来)
四、不须要构建前清除上一次构建内容
五、不须要打包分析
一、webpack的mode属性值设置为production,开启这个会压缩代码。
二、不须要webpack-dev-server和热更新。
三、css要提取到单独文件并压缩
四、须要构建前清除上一次构建内容
五、须要打包分析
好了,大概了解了区别后,咱们还须要单独安装其余没有的依赖,先来波安装命令,一把梭:
npm i clean-webpack-plugin copy-webpack-plugin @intervolga/optimize-cssnano-plugin mini-css-extract-plugin webpack-merge webpack-bundle-analyzer -D
复制代码
接下来修改相关的文件
注:这儿我把miniCssExtractPlugin放在通用配置文件里面了,开发和生产都使用,这里格式有点乱了-_-!
// build/webpack.config.js
// node.js里面自带的操做路径的模块
const path = require("path");
// 引入htmlWebpackPlugin自动导入js文件
const htmlWebpackPlugin = require('html-webpack-plugin');
// 引入vue-loader插件
const VueLoaderPlugin = require('vue-loader/lib/plugin');
// 用于提取css到文件中
const miniCssExtractPlugin = require('mini-css-extract-plugin');
// 用于压缩css代码
const optimizeCssnanoPlugin = require('@intervolga/optimize-cssnano-plugin');
// 拷贝静态资源
const copyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
// webpack打包的入口文件
entry: {
main: path.resolve(__dirname, "../src/main.js")
},
// webpack打包的输出相关的额配置
output: {
// 打包事后的文件的输出的路径
path: path.resolve(__dirname, "../dist"),
// 打包后生成的js文件,带hash值来保证文件的惟一性
filename: "js/[name].[hash:4].js",
// 生成的chunk文件名
chunkFilename: "js/[name].[hash:4].js",
// 资源的引用路径(这个跟你打包上线的配置有关系)
publicPath: "/"
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader'
}
]
},
{
test: /\.(scss|sass)$/,
use: [
{
loader: miniCssExtractPlugin.loader, // 使用miniCssExtractPlugin.loader代替style-loader
},
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
},
{
loader: 'postcss-loader'
}
]
},
{
test: /\.(jpe?g|png|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 5120, // 当文件大于5kb时走file-loader相关的配置
esModule: false, // 这个参数要设置成false,否则生成图片的路径时[object Module]
fallback: 'file-loader', // 当文件大于5kb时走file-loader相关的配置
name: 'images/[name].[hash:4].[ext]' // 生成的路径和文件名
}
}
]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 5120,
esModule: false,
fallback: 'file-loader',
name: 'media/[name].[hash:4].[ext]'
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 5120,
esModule: false,
fallback: 'file-loader',
name: 'fonts/[name].[hash:4].[ext]'
}
}
]
},
{
test: /\.vue$/,
use: [
{
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
}
}
}
]
}
]
},
plugins: [
new htmlWebpackPlugin({
// 指定模板
template: path.resolve(__dirname, '../public/index.html'),
// 输出的文件
filename: path.resolve(__dirname, '../dist/index.html')
}),
new VueLoaderPlugin(),
// 新建miniCssExtractPlugin实例并配置
new miniCssExtractPlugin({
filename: 'css/[name].[hash:4].css',
chunkFilename: 'css/[name].[hash:4].css'
}),
// 压缩css
new optimizeCssnanoPlugin({
sourceMap: true,
cssnanoOptions: {
preset: ['default', {
discardComments: {
removeAll: true,
},
}],
},
}),
// 拷贝静态资源
new copyWebpackPlugin([{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist')
}])
],
resolve: {
alias: {
// 写了这句,咱们能够这样写代码 import Vue from 'vue'
'vue$': 'vue/dist/vue.runtime.esm.js',
// 写了这句,咱们能够这样写代码 import api from '@/api/api.js',省去处处找路径定位到src的麻烦
'@': path.resolve(__dirname, '../src')
},
// 添加一个 resolve.extensions 属性,方便咱们引入依赖或者文件的时候能够省略后缀
// 咱们在引入文件时能够这样写 import api from '@/api/api'。
extensions: ['*', '.js', '.vue']
}
};
复制代码
// build/webpack.dev.js
// 引入webpack
const webpack = require('webpack');
// 引入webpack通用配置
const webpackCommonConfig = require('./webpack.config.js');
// 引入配置合并插件
const merge = require('webpack-merge');
module.exports = merge(webpackCommonConfig, {
// 指定模式,这儿有none production development三个参数可选
// 具体做用请查阅官方文档
mode: "development",
plugins: [
// 辅助HotModuleReplacementPlugin插件
new webpack.NamedModulesPlugin(),
// 启用热更新必须的
new webpack.HotModuleReplacementPlugin(),
],
devServer: {
// 默认状况不设置这个只能经过localhost:9000来访问,如今能够经过本机局域网ip来访问,
// 好比192.168.12.21:9000,手机在这个局网内也能够访问
host: '0.0.0.0',
hot: true,
port: 9200,
contentBase: './dist'
}
});
复制代码
// build/webpack.prod.js
// 引入清除打包后文件的插件(最新版的须要解构,否则会报不是构造函数的错,并且名字必须写CleanWebpackPlugin)
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// 引入配置合并插件
const merge = require('webpack-merge');
// 引入通用配置
const webpackCommonConfig = require('./webpack.config.js');
// 分析打包后模块分析插件
const webpackBundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = merge(webpackCommonConfig, {
// 指定模式,这儿有none production development三个参数可选
// 具体做用请查阅官方文档
mode: "production",
plugins: [
new CleanWebpackPlugin(),
new webpackBundleAnalyzer({
analyzerMode: 'static'
}),
]
});
复制代码
配置好相关文件后咱们再修改下package.json中的scripts:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
},
复制代码
相关配置到这儿就结束了,后期会继续更新优化方面的内容,如代码拆分这些,本篇只是如何来搭建一个环境并跑起来,并无优化相关的内容。若有错误还请你们交流指出,以为不错的话点个赞再走吧!
相关代码我已经提交到仓库了,连接地址