博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
vue-element-admin源码解读——项目启动过程
阅读量:3901 次
发布时间:2019-05-23

本文共 10839 字,大约阅读时间需要 36 分钟。


是一款优秀的前端框架,使用了最新的前端技术,内置了国际化解决方案,动态路由、状态管理等等方案使得整个框架结构非常清晰。不仅如此,该框架还有Typescript的,具有强制类型约束、接口规范统一等等功能,对今后项目的拓展与模块化、与后端的对接等等方面将起到必不可少的作用。

虽然官方也编写了相关的用户指南,不过只是单纯地介绍了如何修改当前项目结构来满足业务需求,并未对项目的代码实现层面做太多的赘述,所以这篇文章就是来从源码的角度,来一步步深入vue-element-admin框架,了解整个项目的构成,来实现更深度地定制。

vue-element-admin是一个具有完备功能的示例框架,如果只需要基础框架结构,请使用

代码入口

根据官方提供的命令在执行npm install模块安装之后,使用npm run dev来启动项目。我们来看下这个dev脚本对应的命令在哪:

// package.json{
..., "scripts": {
"dev": "vue-cli-service serve", "build:prod": "vue-cli-service build", "build:stage": "vue-cli-service build --mode staging", ... }, ...}

可以看到dev脚本启动就是vue-cli-service serve这个控制台命令,我们进一步跟踪看看:

# vue-cli-service#!/bin/shbasedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")case `uname` in    *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;esacif [ -x "$basedir/node" ]; then  "$basedir/node"  "$basedir/../@vue/cli-service/bin/vue-cli-service.js" "$@"  ret=$?else   node  "$basedir/../@vue/cli-service/bin/vue-cli-service.js" "$@"  ret=$?fiexit $ret

从这里就可以看出vue-cli-service serve其实就是执行了node vue-cli-service.js serve这个命令。

// vue-cli-service.jsconst semver = require('semver')const {
error } = require('@vue/cli-shared-utils')const requiredVersion = require('../package.json').engines.nodeif (!semver.satisfies(process.version, requiredVersion)) {
error( `You are using Node ${
process.version}, but vue-cli-service ` + `requires Node ${
requiredVersion}.\nPlease upgrade your Node version.` ) process.exit(1)}const Service = require('../lib/Service')const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd())const rawArgv = process.argv.slice(2)const args = require('minimist')(rawArgv, {
boolean: [ // build 'modern', 'report', 'report-json', 'watch', // serve 'open', 'copy', 'https', // inspect 'verbose' ]})const command = args._[0]service.run(command, args, rawArgv).catch(err => {
error(err) process.exit(1)})

到这里就到命令的最后一步了,调用Service对象的run方法执行对应的指令。在深入这个run方法之前,我们来看一下Service这个对象在初始化的时候做了些什么:

// Service的初始化就是读取了Package.json和Vue.config.js// 文件中的配置信息,这里需要对比着Vue.config.js文件内容看module.exports = class Service {
constructor (context, {
plugins, pkg, inlineOptions, useBuiltIn } = {
}) {
process.VUE_CLI_SERVICE = this this.initialized = false this.context = context this.inlineOptions = inlineOptions this.webpackChainFns = [] this.webpackRawConfigFns = [] this.devServerConfigFns = [] this.commands = {
} this.pkgContext = context //从package.json中获取依赖包信息 this.pkg = this.resolvePkg(pkg) //从package.json中加载插件,同时合并内建插件 this.plugins = this.resolvePlugins(plugins, useBuiltIn) this.modes = this.plugins.reduce((modes, {
apply: {
defaultModes }}) => {
return Object.assign(modes, defaultModes) }, {
}) } ...}// Vue.config.jsmodule.exports = {
//下面这一块都属于inlineOptions publicPath: '/', outputDir: 'dist', assetsDir: 'static', lintOnSave: process.env.NODE_ENV === 'development', productionSourceMap: false, //这是用于配置WebpackDevServer的配置 devServer: {
port: port, open: true, overlay: {
warnings: false, errors: true }, // 这里是为了启动监听服务之前启动mock服务器返回模拟数据 before: require('./mock/mock-server.js') }, // 这一部分配置适用于传递给webpack configureWebpack: {
//这个配置很简单就是为src目录取了个别名 name: name, resolve: {
alias: {
'@': resolve('src') } } }, //这里是配置webpack的loader(资源加载链) chainWebpack(config) {
... }}

看完上面,我们就知道了在Service初始化时会将配置文件中的配置加载到上下文之中,这个过程是属于Vue框架完成的。根据之前传入的参数Serve,可知接下来Servicerun方法肯定会去执行Serve.

async run (name, args = {
}, rawArgv = []) {
const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name]) //这个方法就是加载用户的环境变量配置文件.env this.init(mode) args._ = args._ || [] //到这里我们就可以知道了serve命令被存储在了command中了 let command = this.commands[name] if (!command && name) {
error(`command "${
name}" does not exist.`) process.exit(1) } if (!command || args.help || args.h) {
command = this.commands.help } else {
args._.shift() // remove command itself rawArgv.shift() } const {
fn } = command return fn(args, rawArgv) }

从上面我们知道了Serve命令被存放在了Commands数组中。在读源码的过程中,我好像在resolvePlugins方法中发现了些蛛丝马迹。

resolvePlugins (inlinePlugins, useBuiltIn) {
const idToPlugin = id => ({
id: id.replace(/^.\//, 'built-in:'), apply: require(id) }) let plugins const builtInPlugins = [ './commands/serve', './commands/build', './commands/inspect', './commands/help', // config plugins are order sensitive './config/base', './config/css', './config/dev', './config/prod', './config/app' ].map(idToPlugin) ....}

看来serve命令就存放在同模块下的同目录的command目录下中:

在这里插入图片描述
那这就明了多了。好的,我们就来看看serve.js是如何绑定的。

详解serve.js文件

由于整个文件比较长,我将将其拆分为几个部分来进行说明:

命令注册

api.registerCommand('serve', {
description: 'start development server', usage: 'vue-cli-service serve [options] [entry]', options: {
'--open': `open browser on server start`, '--copy': `copy url to clipboard on server start`, '--mode': `specify env mode (default: development)`, '--host': `specify host (default: ${
defaults.host})`, '--port': `specify port (default: ${
defaults.port})`, '--https': `use https (default: ${
defaults.https})`, '--public': `specify the public network URL for the HMR client` } }, ...}//上述代码就是通过registerCommand将serve命令注册到了command命令中去了。registerCommand (name, opts, fn) {
if (typeof opts === 'function') {
fn = opts opts = null } this.service.commands[name] = {
fn, opts: opts || {
}}}

Serve工作过程

async function serve (args) {
info('Starting development server...') // although this is primarily a dev server, it is possible that we // are running it in a mode with a production env, e.g. in E2E tests. const isInContainer = checkInContainer() const isProduction = process.env.NODE_ENV === 'production' const url = require('url') const chalk = require('chalk') const webpack = require('webpack') const WebpackDevServer = require('webpack-dev-server') const portfinder = require('portfinder') const prepareURLs = require('../util/prepareURLs') const prepareProxy = require('../util/prepareProxy') const launchEditorMiddleware = require('launch-editor-middleware') const validateWebpackConfig = require('../util/validateWebpackConfig') const isAbsoluteUrl = require('../util/isAbsoluteUrl') // 获取webpack的配置,在Service初始化时就获取了 const webpackConfig = api.resolveWebpackConfig() //检查配置信息是否有问题 validateWebpackConfig(webpackConfig, api, options) //配置webpack的devServer配置选项,这个选项是从Vue.config.js获取的 const projectDevServerOptions = Object.assign( webpackConfig.devServer || {
}, options.devServer ) ...... // 配置服务器的选项 const useHttps = args.https || projectDevServerOptions.https || defaults.https const protocol = useHttps ? 'https' : 'http' const host = args.host || process.env.HOST || projectDevServerOptions.host || defaults.host portfinder.basePort = args.port || process.env.PORT || projectDevServerOptions.port || defaults.port const port = await portfinder.getPortPromise() const rawPublicUrl = args.public || projectDevServerOptions.public const publicUrl = rawPublicUrl ? /^[a-zA-Z]+:\/\//.test(rawPublicUrl) ? rawPublicUrl : `${
protocol}://${
rawPublicUrl}` : null const urls = prepareURLs( protocol, host, port, isAbsoluteUrl(options.publicPath) ? '/' : options.publicPath ) const proxySettings = prepareProxy( projectDevServerOptions.proxy, api.resolve('public') ) // 配置webpack-dev-server选项 if (!isProduction) {
const sockjsUrl = publicUrl ? `?${
publicUrl}/sockjs-node` : isInContainer ? `` : `?` + url.format({
protocol, port, hostname: urls.lanUrlForConfig || 'localhost', pathname: '/sockjs-node' }) const devClients = [ // dev server client require.resolve(`webpack-dev-server/client`) + sockjsUrl, // hmr client require.resolve(projectDevServerOptions.hotOnly ? 'webpack/hot/only-dev-server' : 'webpack/hot/dev-server') // TODO custom overlay client // `@vue/cli-overlay/dist/client` ] if (process.env.APPVEYOR) {
devClients.push(`webpack/hot/poll?500`) } // inject dev/hot client addDevClientToEntry(webpackConfig, devClients) } // create compiler const compiler = webpack(webpackConfig) // 创建服务器,并注入配置信息 const server = new WebpackDevServer(compiler, Object.assign({
clientLogLevel: 'none', historyApiFallback: {
disableDotRule: true, rewrites: genHistoryApiFallbackRewrites(options.publicPath, options.pages) }, //指定根目录路径 contentBase: api.resolve('public'), //启动是否监视根目录文件变化 watchContentBase: !isProduction, //开发环境下启动热更新 hot: !isProduction, quiet: true, compress: isProduction, publicPath: options.publicPath, overlay: isProduction // TODO disable this ? false : {
warnings: false, errors: true } }, projectDevServerOptions, {
https: useHttps, proxy: proxySettings, before (app, server) {
// launch editor support. // this works with vue-devtools & @vue/cli-overlay app.use('/__open-in-editor', launchEditorMiddleware(() => console.log( `To specify an editor, sepcify the EDITOR env variable or ` + `add "editor" field to your Vue project config.\n` ))) //指定vue.config.js中配置的插件 api.service.devServerConfigFns.forEach(fn => fn(app, server)) //应用项目中配置的中间件,vue.config.js中的devServer.before中指定的mock服务器就是在这被执行的 projectDevServerOptions.before && projectDevServerOptions.before(app, server) } })) // 监听系统信号 ;['SIGINT', 'SIGTERM'].forEach(signal => {
process.on(signal, () => {
server.close(() => {
process.exit(0) }) }) }) //监听关闭信号 if (process.env.VUE_CLI_TEST) {
process.stdin.on('data', data => {
if (data.toString() === 'close') {
console.log('got close signal!') server.close(() => {
process.exit(0) }) } }) }

在上面的serve函数中该被加载的插件和中间件都被执行了之后,将会返回一个Promise对象用于启动http监听。

return new Promise((resolve, reject) => {
//这里省略了很大部分console.log调试信息的输出 server.listen(port, host, err => {
if (err) {
reject(err) } }) })

到这里呢,整个框架的基础工作就完成了,包括插件、中间件(Mock服务器)、配置信息的加载、服务监听的打开。

转载地址:http://oqfen.baihongyu.com/

你可能感兴趣的文章
听说只有大厂的Android工程师才能全答对这20道题?我看你在吹牛哦!
查看>>
武功秘籍之 Redis 面试题全掌握,学完马上找面试官对线!
查看>>
50道!2020年!!MySQL高频数据库面试题解析,你都懂了吗?
查看>>
如何用Spring Boot加密配置文件中的特殊内容示例代码详解
查看>>
谈谈这些年面试官给大伙下的那些套,如何解?(面试技巧)
查看>>
5年开发经验的我被几条朋友圈打击到,点燃自己冲击阿里面经!
查看>>
5年工作经验的我放弃安逸,一份来自腾讯魔鬼面试的终极考验!
查看>>
学JAVA吗同学,这篇Sping boot 确定不了解下么?
查看>>
(3年+offer)华为技术岗面试初面+综合面试经验总结
查看>>
男默女泪,努力复习的我终于通过社招进入BAT工作了!(JAVA+JVM+框架+中间件+Spring干货分享)
查看>>
Python 导包
查看>>
dok_matrix
查看>>
theano 后端爆内存
查看>>
os.environ 和 keras.json
查看>>
后台面试经典问题-手写LRU算法
查看>>
Part-Guided Attention Learning for Vehicle Instance Retrieval
查看>>
Deep Residual Learning for Image Recognition
查看>>
Bag of Tricks and A Strong Baseline for Deep Person Re-identification
查看>>
vue+flask实现视频目标检测yolov5
查看>>
关于BigInteger
查看>>