vue-element-admin Is an excellent front-end framework, using the latest front-end technology, built-in (Vue-i18) Internationalized Solutions, Dynamic Routing (Vue Route) , State Management (Vuex) The scheme such as these makes the whole frame structure very clear.What's more, the framework also has Typescript's Implementation Version (vue-typescript-admin-template) With the functions of enforcing type constraints, unifying interface specifications, etc., it will play an essential role in the expansion and modularization of future projects, docking with backend, etc.
although vue-element-admin The official user guide has also been written, but it simply describes how to modify the current project structure to meet business needs without too much detail on the code implementation level of the project, so this article is to step into the vue-element-admin framework from the source point of view to understand the composition of the entire project to achieve deeper customization.
vue-element-admin is a fully functional sample framework, if only the infrastructure is required, use vue-admin-template
Code Entry
Use npm run dev to start the project after executing the npm install module installation according to an official command.Let's see where the command for this dev script is:
// package.json { ..., "scripts": { "dev": "vue-cli-service serve", "build:prod": "vue-cli-service build", "build:stage": "vue-cli-service build --mode staging", ... }, ... }
As you can see that the dev script startup is the console command vue-cli-service, let's take a closer look:
# vue-cli-service #!/bin/sh basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") case `uname` in *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;; esac if [ -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=$? fi exit $ret
From here you can see that vue-cli-service serve r actually executes node vue-cli-Service.jsServer this command.
// vue-cli-service.js const semver = require('semver') const { error } = require('@vue/cli-shared-utils') const requiredVersion = require('../package.json').engines.node if (!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) })
This is the last step of the command, calling the run method of the Service object to execute the corresponding command.Before going into this run method, let's look at what the Service object did when it was initialized:
// Service initialization is read Package.json andVue.config.js // Configuration information in the file, which needs to be compared here Vue.config.js File Content View 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 //fromPackage.jsonGet dependent package information in this.pkg = this.resolvePkg(pkg) //fromPackage.jsonLoad plug-ins in and merge built-in plug-ins this.plugins = this.resolvePlugins(plugins, useBuiltIn) this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => { return Object.assign(modes, defaultModes) }, {}) } ... } // Vue.config.js module.exports = { //The following block belongs to inlineOptions publicPath: '/', outputDir: 'dist', assetsDir: 'static', lintOnSave: process.env.NODE_ENV === 'development', productionSourceMap: false, //This is the configuration used to configure WebpackDevServer devServer: { port: port, open: true, overlay: { warnings: false, errors: true }, // This is to start the mock server to return simulated data before starting the listening service before: require('./mock/mock-server.js') }, // This part of the configuration applies to delivering to the webpack configureWebpack: { //This configuration is simple enough to alias the src directory name: name, resolve: { alias: { '@': resolve('src') } } }, //Here is the loader for configuring the webpack (resource load chain) chainWebpack(config) { ... } }
Looking at the above, we know that the configuration in the configuration file is loaded into the context when the service is initialized, which is done by the Vue framework.Based on the parameter Serve passed in earlier, you know that the next Service run method will definitely execute the Serve.
async run (name, args = {}, rawArgv = []) { const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name]) //This is done by loading the user's environment variable profile.env this.init(mode) args._ = args._ || [] //So here we know that the serve r command is stored in the 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) }
From above we know that the Serve command is stored in the Commands array.While reading the source code, I seem to have found some clues in the resolvePlugins method.
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) .... }
It appears that the serve r command is stored in the same directory command directory under the same module:
[External chain picture transfer failed, source station may have anti-theft chain mechanism, it is recommended to save the picture and upload it directly (img-lFaHgXUf-1591717893053)(./command.png)], that's a lot clearer.Okay, let's have a lookServe.jsHow to bind.
DetailedServe.jsfile
Because the whole file is long, I'll break it up into several parts to illustrate:
Command Registration
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` } }, ... } //The above code registers the serve r command with the command command through registerCommand. registerCommand (name, opts, fn) { if (typeof opts === 'function') { fn = opts opts = null } this.service.commands[name] = { fn, opts: opts || {}} }
Serve Work Process
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') // Get the configuration of the webpack, which you get when Service initializes const webpackConfig = api.resolveWebpackConfig() //Check configuration information for problems validateWebpackConfig(webpackConfig, api, options) //Configure the devServer configuration option for the webpack from Vue.config.js Acquired const projectDevServerOptions = Object.assign( webpackConfig.devServer || {}, options.devServer ) ...... // Options for configuring the server 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') ) // Configure webpack-dev-server options 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) // Create a server and inject configuration information const server = new WebpackDevServer(compiler, Object.assign({ clientLogLevel: 'none', historyApiFallback: { disableDotRule: true, rewrites: genHistoryApiFallbackRewrites(options.publicPath, options.pages) }, //Specify root directory path contentBase: api.resolve('public'), //Startup monitors root file changes watchContentBase: !isProduction, //Start hot update in development environment 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` ))) //AppointVue.config.jsPlug-ins configured in api.service.devServerConfigFns.forEach(fn => fn(app, server)) //Apply the middleware configured in the project,Vue.config.jsInDevServer.beforeThis is where the mock server specified in the projectDevServerOptions.before && projectDevServerOptions.before(app, server) } })) // Monitor System Signals ;['SIGINT', 'SIGTERM'].forEach(signal => { process.on(signal, () => { server.close(() => { process.exit(0) }) }) }) //Listen for shutdown signal 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) }) } }) }
After the loaded plug-in and middleware are executed in the serve r function above, a Promise object is returned to start http listening.
return new Promise((resolve, reject) => { //A large part is omitted hereConsole.logOutput of debug information server.listen(port, host, err => { if (err) { reject(err) } }) })
Here, the basic work of the whole framework is completed, including plug-ins, middleware (Mock server), loading of configuration information, opening of service monitoring.