一个vue-cli的项目结构如下:
目录
结构预览
├─build // 保存一些webpack的初始化配置,项目构建 │ ├─build.js // 生产环境构建 │ ├─check-version.js // 检查npm、node版本 │ ├─vue-loader.conf.js // webpack loader配置 │ ├─webpack.base.conf.js// webpack基础配置 │ ├─webpack.dev.conf.js // 开发环境配置,构建本地开发服务器 │ ├─webpack.prod.conf.js// 生产环境的配置 │ ├─config // config文件夹保存一些项目初始化的配置 │ ├─dev.env.js // 开发环境的配置 │ ├─index.js // 项目一些配置变量 │ ├─prod.env.js // 生产环境的配置 │ ├─dist // 打包后的项目 ├─node_modules // 依赖包 │ ├─src // 源码目录 │ ├─assets // 静态文件目录 │ ├─components // 组件文件 │ ├─router // 路由 │ ├─App.vue // 是项目入口文件 │ ├─main.js // 是项目的核心文件,入口 ├─static // 静态资源目录 ├─.babelrc // Babel的配置文件 ├─.editorconfig // 代码规范配置文件 ├─.gitignore // git忽略配置文件 ├─.postcssrc.js // postcss插件配置文件 ├─index.html // 页面入口文件 ├─package-lock.json // 项目包管控文件 ├─package.json // 项目配置 └─README.md // 项目说明书
结构解析
build
dev-server.js
首先来看执行”npm run dev”时候最先执行的build/dev-server.js文件。该文件主要完成下面几件事情:
说明: express服务器提供静态文件服务,不过它还使用了http-proxy-middleware,一个http请求代理的中间件。前端开发过程中需要使用到后台的API的话,可以通过配置proxyTable来将相应的后台请求代理到专用的API服务器。
// 检查NodeJS和npm的版本 require('./check-versions')() // 获取基本配置 var config = require('../config') // 如果Node的环境变量中没有设置当前的环境(NODE_ENV),则使用config中的dev环境配置作为当前的环境 if (!process.env.NODE_ENV) { process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) } // opn是一个可以调用默认软件打开网址、图片、文件等内容的插件 // 这里用它来调用默认浏览器打开dev-server监听的端口,例如:localhost:8080 var opn = require('opn') var path = require('path') var express = require('express') var webpack = require('webpack') // http-proxy-middleware是一个express中间件,用于将http请求代理到其他服务器 // 例:localhost:8080/api/xxx --> localhost:3000/api/xxx // 这里使用该插件可以将前端开发中涉及到的请求代理到提供服务的后台服务器上,方便与服务器对接 var proxyMiddleware = require('http-proxy-middleware') // 开发环境下的webpack配置 var webpackConfig = require('./webpack.dev.conf') // dev-server 监听的端口,如果没有在命令行传入端口号,则使用config.dev.port设置的端口,例如8080 var port = process.env.PORT || config.dev.port // 用于判断是否要自动打开浏览器的布尔变量,当配置文件中没有设置自动打开浏览器的时候其值为 false var autoOpenBrowser = !!config.dev.autoOpenBrowser // HTTP代理表,指定规则,将某些API请求代理到相应的服务器 var proxyTable = config.dev.proxyTable // 创建express服务器 var app = express() // webpack根据配置开始编译打包源码并返回compiler对象 var compiler = webpack(webpackConfig) // webpack-dev-middleware将webpack编译打包后得到的产品文件存放在内存中而没有写进磁盘 // 将这个中间件挂到express上使用之后即可提供这些编译后的产品文件服务 var devMiddleware = require('webpack-dev-middleware')(compiler, { publicPath: webpackConfig.output.publicPath, // 设置访问路径为webpack配置中的output里面所对应的路径 quiet: true // 设置为true,使其不要在控制台
webpack.base.conf.js
从代码中看到,dev-server使用的webpack配置来自build/webpack.dev.conf.js文件(测试环境下使用的是build/webpack.prod.conf.js,这里暂时不考虑测试环境)。而build/webpack.dev.conf.js中又引用了webpack.base.conf.js,所以这里我先分析webpack.base.conf.js。
webpack.base.conf.js主要完成了下面这些事情:
说明: 这个配置里面只配置了.js、.vue、图片、字体等几类文件的处理规则,如果需要处理其他文件可以在module.rules里面另行配置。
var path = require('path') var fs = require('fs') var utils = require('./utils') var config = require('../config') var vueLoaderConfig = require('./vue-loader.conf') // 获取绝对路径 function resolve (dir) { return path.join(__dirname, '..', dir) } module.exports = { // webpack入口文件 entry: { app: './src/main.js' }, // webpack
webpack.dev.conf.js
接下来看webpack.dev.conf.js,这里面在webpack.base.conf的基础上增加完善了开发环境下面的配置,主要包括下面几件事情:
var utils = require('./utils') var webpack = require('webpack') var config = require('../config') // webpack-merge是一个可以合并数组和对象的插件 var merge = require('webpack-merge') var baseWebpackConfig = require('./webpack.base.conf') // html-webpack-plugin用于将webpack编译打包后的产品文件注入到html模板中 // 即自动在index.html里面加上<link>和<script>标签引用webpack打包后的文件 var HtmlWebpackPlugin = require('html-webpack-plugin') // friendly-errors-webpack-plugin用于更友好地
utils
此配置文件是vue开发环境的wepack相关配置文件,主要用来处理css-loader和vue-style-loader
// 引入nodejs路径模块 var path = require('path') // 引入config目录下的index.js配置文件 var config = require('../config') // 引入extract-text-webpack-plugin插件,用来将css提取到单独的css文件中 // 详情请看(1) var ExtractTextPlugin = require('extract-text-webpack-plugin') // exports其实就是一个对象,用来导出方法的最终还是使用module.exports,此处导出assetsPath exports.assetsPath = function (_path) { // 如果是生产环境assetsSubDirectory就是'static',否则还是'static',哈哈哈 var assetsSubDirectory = process.env.NODE_ENV === 'production' ? config.build.assetsSubDirectory : config.dev.assetsSubDirectory // path.join和path.posix.join的区别就是,前者返回的是完整的路径,后者返回的是完整路径的相对根路径 // 也就是说path.join的路径是C:a/a/b/xiangmu/b,那么path.posix.join就是b return path.posix.join(assetsSubDirectory, _path) // 所以这个方法的作用就是返回一个干净的相对根路径 } // 下面是导出cssLoaders的相关配置 exports.cssLoaders = function (options) { // options如果没值就是空对象 options = options || {} // cssLoader的基本配置 var cssLoader = { loader: 'css-loader', options: { // options是用来传递参数给loader的 // minimize表示压缩,如果是生产环境就压缩css代码 minimize: process.env.NODE_ENV === 'production', // 是否开启cssmap,默认是false sourceMap: options.sourceMap } } // generate loader string to be used with extract text plugin function generateLoaders (loader, loaderOptions) { // 将上面的基础cssLoader配置放在一个数组里面 var loaders = [cssLoader] // 如果该函数传递了单独的loader就加到这个loaders数组里面,这个loader可能是less,sass之类的 if (loader) { loaders.push({ // 加载对应的loader loader: loader + '-loader', // Object.assign是es6的方法,主要用来合并对象的,浅拷贝 options: Object.assign({}, loaderOptions, { sourceMap: options.sourceMap }) }) } // Extract CSS when that option is specified // (which is the case during production build) // 注意这个extract是自定义的属性,可以定义在options里面,主要作用就是当配置为true就把文件单独提取,false表示不单独提取,这个可以在使用的时候单独配置,瞬间觉得vue作者好牛逼 if (options.extract) { return ExtractTextPlugin.extract({ use: loaders, fallback: 'vue-style-loader' }) } else { return ['vue-style-loader'].concat(loaders) } // 上面这段代码就是用来返回最终读取和导入loader,来处理对应类型的文件 } // https://vue-loader.vuejs.org/en/configurations/extract-css.html return { css: generateLoaders(), // css对应 vue-style-loader 和 css-loader postcss: generateLoaders(), // postcss对应 vue-style-loader 和 css-loader less: generateLoaders('less'), // less对应 vue-style-loader 和 less-loader sass: generateLoaders('sass', { indentedSyntax: true }), // sass对应 vue-style-loader 和 sass-loader scss: generateLoaders('sass'), // scss对应 vue-style-loader 和 sass-loader stylus: generateLoaders('stylus'), // stylus对应 vue-style-loader 和 stylus-loader styl: generateLoaders('stylus') // styl对应 vue-style-loader 和 styl-loader } } // Generate loaders for standalone style files (outside of .vue) // 下面这个主要处理import这种方式导入的文件类型的打包,上面的exports.cssLoaders是为这一步服务的 exports.styleLoaders = function (options) { var output = [] // 下面就是生成的各种css文件的loader对象 var loaders = exports.cssLoaders(options) for (var extension in loaders) { // 把每一种文件的laoder都提取出来 var loader = loaders[extension] output.push({ // 把最终的结果都push到output数组中,大事搞定 test: new RegExp('\\.' + extension + '$'), use: loader }) } return output }
extract-text-webpack-plugin插件是用来将文本从bundle中提取到一个单独的文件中
const ExtractTextPlugin = require("extract-text-webpack-plugin"); module.exports = { module: { rules: [ { test: /\.css$/, //主要用来处理css文件 use: ExtractTextPlugin.extract({ fallback: "style-loader", // fallback表示如果css文件没有成功导入就使用style-loader导入 use: "css-loader" // 表示使用css-loader从js读取css文件 }) } ], plugins: [ new ExtractTextPlugin("styles.css") //表示生成styles.css文件 ] } }
vue-loader.conf.js
var utils = require('./utils') var config = require('../config') var isProduction = process.env.NODE_ENV === 'production' module.exports = { // 处理.vue文件中的样式 loaders: utils.cssLoaders({ // 是否打开source-map sourceMap: isProduction ? config.build.productionSourceMap : config.dev.cssSourceMap, // 是否提取样式到单独的文件 extract: isProduction }), transformToRequire: { video: 'src', source: 'src', img: 'src', image: 'xlink:href' } }
dev-client.js
dev-client.js里面主要写了浏览器端代码,用于实现webpack的热更新。
/* eslint-disable */ // 实现浏览器端的EventSource,用于跟服务器双向通信 // webpack热重载客户端跟dev-server上的热重载插件之间需要进行双向通信 // 服务端webpack重新编译后,会向客户端推送信息,告诉客户端进行更新 require('eventsource-polyfill') // webpack热重载客户端 var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') // 客户端收到更新动作,执行页面刷新 hotClient.subscribe(function (event) { if (event.action === 'reload') { window.location.reload() } })
build.js
执行”npm run build”的时候首先执行的是build/build.js文件,build.js主要完成下面几件事:
说明: webpack编译之后会输出到配置里面指定的目标文件夹;删除目标文件夹之后再创建是为了去除旧的内容,以免产生不可预测的影响。
// 检查NodeJS和npm的版本 require('./check-versions')() process.env.NODE_ENV = 'production' // ora,一个可以在终端显示spinner的插件 var ora = require('ora') // rm,用于删除文件或文件夹的插件 var rm = require('rimraf') var path = require('path') // chalk,用于在控制台
webpack.prod.conf.js
构建的时候用到的webpack配置来自webpack.prod.conf.js,该配置同样是在webpack.base.conf基础上的进一步完善。主要完成下面几件事情:
说明: webpack插件里面多了丑化压缩代码以及抽离css文件等插件。
var path = require('path') var utils = require('./utils') var webpack = require('webpack') var config = require('../config') var merge = require('webpack-merge') var baseWebpackConfig = require('./webpack.base.conf') // copy-webpack-plugin,用于将static中的静态文件复制到产品文件夹dist var CopyWebpackPlugin = require('copy-webpack-plugin') var HtmlWebpackPlugin = require('html-webpack-plugin') var ExtractTextPlugin = require('extract-text-webpack-plugin') // optimize-css-assets-webpack-plugin,用于优化和最小化css资源 var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') var env = config.build.env var webpackConfig = merge(baseWebpackConfig, { module: { // 样式文件的处理规则,对css/sass/scss等不同内容使用相应的styleLoaders // 由utils配置出各种类型的预处理语言所需要使用的loader,例如sass需要使用sass-loader rules: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) }, // 是否使用source-map devtool: config.build.productionSourceMap ? '#source-map' : false, // webpack
check-versions.js
// chalk, 用于在控制台
config
index.js
config文件夹下最主要的文件就是index.js了,在这里面描述了开发和构建两种环境下的配置,前面的build文件夹下也有不少文件引用了index.js里面的配置
// see http://vuejs-templates.github.io/webpack for documentation. var path = require('path') module.exports = { // 构建产品时使用的配置 build: { // 环境变量 env: require('./prod.env'), // html入口文件 index: path.resolve(__dirname, '../dist/index.html'), // 产品文件的存放路径 assetsRoot: path.resolve(__dirname, '../dist'), // 二级目录,存放静态资源文件的目录,位于dist文件夹下 assetsSubDirectory: 'static', // 发布路径,如果构建后的产品文件有用于发布CDN或者放到其他域名的服务器,可以在这里进行设置 // 设置之后构建的产品文件在注入到index.html中的时候就会带上这里的发布路径 assetsPublicPath: '/', // 是否使用source-map productionSourceMap: true, // Gzip off by default as many popular static hosts such as // Surge or Netlify already gzip all static assets for you. // Before setting to `true`, make sure to: // npm install --save-dev compression-webpack-plugin // 是否开启gzip压缩 productionGzip: false, // gzip模式下需要压缩的文件的扩展名,设置js、css之后就只会对js和css文件进行压缩 productionGzipExtensions: ['js', 'css'], // Run the build command with an extra argument to // View the bundle analyzer report after build finishes: // `npm run build --report` // Set to `true` or `false` to always turn it on or off // 是否展示webpack构建打包之后的分析报告 bundleAnalyzerReport: process.env.npm_config_report }, // 开发过程中使用的配置 dev: { // 环境变量 env: require('./dev.env'), // dev-server监听的端口 port: 8080, // 是否自动打开浏览器 autoOpenBrowser: true, // 静态资源文件夹 assetsSubDirectory: 'static', // 发布路径 assetsPublicPath: '/', // 代理配置表,在这里可以配置特定的请求代理到对应的API接口 // 例如将'localhost:8080/api/xxx'代理到'www.example.com/api/xxx' proxyTable: {}, // CSS Sourcemaps off by default because relative paths are "buggy" // with this option, according to the CSS-Loader README // (https://github.com/webpack/css-loader#sourcemaps) // In our experience, they generally work as expected, // just be aware of this issue when enabling this option. // 是否开启 cssSourceMap cssSourceMap: false } } 'use strict' const path = require('path') module.exports = { dev: { // 开发环境下面的配置 assetsSubDirectory: 'static',//子目录,一般存放css,js,image等文件 assetsPublicPath: '/',//根目录 proxyTable: {},//可利用该属性解决跨域的问题 host: 'localhost', // 地址 port: 8080, //端口号设置,端口号占用出现问题可在此处修改 autoOpenBrowser: false,//是否在编译(输入命令行npm run dev)后打开http://localhost:8080/页面,以前配置为true,近些版本改为false,个人偏向习惯自动打开页面 errorOverlay: true,//浏览器错误提示 notifyOnErrors: true,//跨平台错误提示 poll: false, //使用文件系统(file system)获取文件改动的通知devServer.watchOptions devtool: 'cheap-module-eval-source-map',//增加调试,该属性为原始源代码(仅限行)不可在生产环境中使用 cacheBusting: true,//使缓存失效 cssSourceMap: true//代码压缩后进行调bug定位将非常困难,于是引入sourcemap记录压缩前后的位置信息记录,当产生错误时直接定位到未压缩前的位置,将大大的方便我们调试 }, build: { // 生产环境下面的配置 index: path.resolve(__dirname, '../dist/index.html'),//index编译后生成的位置和名字,根据需要改变后缀,比如index.php assetsRoot: path.resolve(__dirname, '../dist'),//编译后存放生成环境代码的位置 assetsSubDirectory: 'static',//js,css,images存放文件夹名 assetsPublicPath: '/',//发布的根目录,通常本地打包dist后打开文件会报错,此处修改为./。如果是上线的文件,可根据文件存放位置进行更改路径 productionSourceMap: true, devtool: '#source-map',//① //unit的gzip命令用来压缩文件,gzip模式下需要压缩的文件的扩展名有js和css productionGzip: false, productionGzipExtensions: ['js', 'css'], bundleAnalyzerReport: process.env.npm_config_report } }
prod.env.js
当开发是调取dev.env.js的开发环境配置,发布时调用prod.env.js的生产环境配置
'use strict' module.exports = { NODE_ENV: '"production"' }
dev.env.js
config内的文件其实是服务于build的,大部分是定义一个变量export出去。
'use strict'//采用严格模式 const merge = require('webpack-merge')//① const prodEnv = require('./prod.env') //webpack-merge提供了一个合并函数,它将数组和合并对象创建一个新对象。 //如果遇到函数,它将执行它们,通过算法运行结果,然后再次将返回的值封装在函数中.这边将dev和prod进行合并 module.exports = merge(prodEnv, { NODE_ENV: '"development"' })
src
①、assets文件:脚手架自动会放入一个图片在里面作为初始页面的logo。平常我们使用的时候会在里面建立js,css,img,fonts等文件夹,作为静态资源调用
②、components文件夹:用来存放组件,合理地使用组件可以高效地实现复用等功能,从而更好地开发项目。一般情况下比如创建头部组件的时候,我们会新建一个header的文件夹,然后再新建一个header.vue的文件
③、router文件夹:该文件夹下有一个叫index.js文件,用于实现页面的路由跳转,具体使用请点击→vue-router传送门
④、App.vue:作为我们的主组件,可通过使用<router-view/>开放入口让其他的页面组件得以显示。
⑤、main.js:作为我们的入口文件,主要作用是初始化vue实例并使用需要的插件,小型项目省略router时可放在该处
.babelrc
{ //制定转码的规则 "presets": [ //env是使用babel-preset-env插件将js进行转码成es5,并且设置不转码的AMD,COMMONJS的模块文件,制定浏览器的兼容 ["env", { "modules": false, "targets": { "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] } }], "stage-2" ], "plugins": ["transform-vue-jsx", "transform-runtime"]//① }
.postcessrc.js
.postcssrc.js文件其实是postcss-loader包的一个配置,在webpack的旧版本可以直接在webpack.config.js中配置,现版本中postcss的文档示例独立出.postcssrc.js,里面写进去需要使用到的插件
module.exports = { "plugins": { "postcss-import": {},//① "postcss-url": {},//② "autoprefixer": {}//③ } }
package.json
package.json来制定名单,需要哪些npm包来参与到项目中来,npm install命令根据这个配置文件增减来管理本地的安装包
{ //从name到private都是package的配置信息,也就是我们在脚手架搭建中输入的项目描述 "name": "shop",//项目名称:不能以.(点)或者_(下划线)开头,不能包含大写字母,具有明确的的含义与现有项目名字不重复 "version": "1.0.0",//项目版本号:遵循“大版本.次要版本.小版本” "description": "A Vue.js project",//项目描述 "author": "qietuniu",//作者名字 "private": true,//是否私有 //scripts中的子项即是我们在控制台运行的脚本的缩写 "scripts": { //①webpack-dev-server:启动了http服务器,实现实时编译; //inline模式会在webpack.config.js入口配置中新增webpack-dev-server/client?http://localhost:8080/的入口,使得我们访问路径为localhost:8080/index.html(相应的还有另外一种模式Iframe); //progress:显示打包的进度 "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev",//与npm run dev相同,直接运行开发环境 "build": "node build/build.js"//使用node运行build文件 }, //②dependencies(项目依赖库):在安装时使用--save则写入到dependencies "dependencies": { "vue": "^2.5.2",//vue.js "vue-router": "^3.0.1"//vue的路由插件 }, //和devDependencies(开发依赖库):在安装时使用--save-dev将写入到devDependencies "devDependencies": { "autoprefixer": "^7.1.2",//autoprefixer作为postcss插件用来解析CSS补充前缀,例如 display: flex会补充为display:-webkit-box;display: -webkit-flex;display: -ms-flexbox;display: flex。 //babel:以下几个babel开头的都是针对es6解析的插件。用最新标准编写的 JavaScript 代码向下编译成可以在今天随处可用的版本 "babel-core": "^6.22.1",//babel的核心,把 js 代码分析成 ast ,方便各个插件分析语法进行相应的处理。 "babel-helper-vue-jsx-merge-props": "^2.0.3",//预制babel-template函数,提供给vue,jsx等使用 "babel-loader": "^7.1.1",//使项目运行使用Babel和webpack来传输js文件,使用babel-core提供的api进行转译 "babel-plugin-syntax-jsx": "^6.18.0",//支持jsx "babel-plugin-transform-runtime": "^6.22.0",//避免编译
注释:
①、点这里→webpack运行时的配置文档传送门
②、devDependencies和dependencies的区别: devDependencies里面的插件只用于开发环境,不用于生产环境,即辅助作用,打包的时候需要,打包完成就不需要了。而dependencies是需要发布到生产环境的,自始至终都在。比如wepack等只是在开发中使用的包就写入到devDependencies,而像vue这种项目全程依赖的包要写入到dependencies
点这里→更多安装包文档搜索页传送门
③、file-loader和url-loader的区别:以图片为例,file-loader可对图片进行压缩,但是还是通过文件路径进行引入,当http请求增多时会降低页面性能,而url-loader通过设定limit参数,小于limit字节的图片会被转成base64的文件,大于limit字节的将进行图片压缩的操作。总而言之,url-loader是file-loader的上层封装。
点这里→file-loader 和 url-loader详解
点这里→file-loader文档传送门
点这里→url-loader文档传送门
其他文件
①、.editorconfig:编辑器的配置文件
②、.gitignore:忽略git提交的一个文件,配置之后提交时将不会加载忽略的文件
③、index.html:页面入口,经过编译之后的代码将插入到这来。
④、package.lock.json:锁定安装时的包的版本号,并且需要上传到git,以保证其他人在npm install时大家的依赖能保证一致
⑤、README.md:可此填写项目介绍
⑥、node_modules:根据package.json安装时候生成的的依赖(安装包)