使用了近一年的vue-cli, 一直都不知道npm run dev 以後發生了些什麼???隨手記錄下學習筆記javascript
從package.json裏面能夠看到npm run dev
其實就是vue-cli-service serve
css
vue-cli3.0 安裝的時候把vue-cli-service一併安裝了,即執行了npm install vue-cli-service --save-dev
html
這樣就能夠在./node_modules/.bin目錄下查看到vue-cli-servicevue
@IF EXIST "%~dp0\node.exe" ( "%~dp0\node.exe" "%~dp0\..\@vue\cli-service\bin\vue-cli-service.js" %* ) ELSE ( @SETLOCAL @SET PATHEXT=%PATHEXT:;.JS;=;% node "%~dp0\..\@vue\cli-service\bin\vue-cli-service.js" %* )
入口:./node_modules/@vue/cli-service/bin/vue-cli-service.js
瞅一眼vue-cli-service.js的核心代碼java
const Service = require('../lib/Service') // 實例化Service // VUE_CLI_CONTEXT爲undefined,因此傳入的值爲process.cwd()及項目所在目錄 const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd()) const rawArgv = process.argv.slice(2) // minimist解析工具來對命令行參數進行解析 const args = require('minimist')(rawArgv, { boolean: [ // build 'modern', 'report', 'report-json', 'watch', // serve 'open', 'copy', 'https', // inspect 'verbose' ] }) // { // _: [ 'serve' ], // modern: false, // report: false, // 'report-json': false, // watch: false, // open: false, // copy: false, // https: false, // verbose: false , // } const command = args._[0] // 執行service方法傳入:'serve'、agrs、['serve','--open',...] service.run(command, args, rawArgv).catch(err => { error(err) process.exit(1) })
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中找到的插件 // 最終獲得的plugins爲內置插件+@vue/cli-plugin-* // {id: 'xxx',apply: require('xxx')} this.plugins = this.resolvePlugins(plugins, useBuiltIn) // 解析每一個命令使用的默認模式 //{ serve: 'development', // build: 'production', // inspect: 'development' } this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => { return Object.assign(modes, defaultModes) }, {}) } // ...... }
read-pkg
這個包讀取package.json文件,並以JSON格式返回賦值給this.pkg。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) if (inlinePlugins) { // ... } else { // 開發環境依賴+生產環境的依賴中,獲取cli-plugin-*插件 const projectPlugins = Object.keys(this.pkg.devDependencies || {}) .concat(Object.keys(this.pkg.dependencies || {})) .filter(isPlugin) // 驗證是否爲vue-cli插件 cli-plugin-* .map(idToPlugin) plugins = builtInPlugins.concat(projectPlugins) } // ... }
// 解析每一個命令使用的默認模式 //{ serve: 'development', // build: 'production', // inspect: 'development' } this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => { return Object.assign(modes, defaultModes) }, {})
內聯插件默認導出了一個defaultModesnode
// serve.js module.exports.defaultModes = { serve: 'development' }
爲何要初始化模式?留個小問題webpack
minimist解析工具來對命令行參數進行解析。shell命令攜帶的參數解析出來。web
const args = require('minimist')(rawArgv, { boolean: [ // build // serve 'open', 'copy', 'https', ] }) // 'serve'、agrs、['serve','--open',...]
運行service.run方法,加載環境變量,加載用戶配置,應用插件。vue-cli
async run (name, args = {}, rawArgv = []) { const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name]) // 加載環境變量,加載用戶配置,應用插件 this.init(mode) args._ = args._ || [] // 註冊完的命令集裏獲取對應的命令 let command = this.commands[name] // .... const { fn } = command return fn(args, rawArgv) }
init (mode = process.env.VUE_CLI_MODE) { // 加載development.env環境變量 if (mode) { this.loadEnv(mode) } this.loadEnv() // ... } // 加載環境變量方法(部分) loadEnv (mode) { // 項目路徑/.env.development const basePath = path.resolve(this.context, `.env${mode ? `.${mode}` : ``}`) // 項目路徑/.env.development.local const localPath = `${basePath}.local` const load = path => { const env = dotenv.config({ path, debug: process.env.DEBUG }) // 這裏會先調用 dotenv(位於 node_modules/dotenv )的 config 函數,最終會返回這樣的格式 { parsed: { YOUR_ENV_KEY: '你設定的環境變量值' } },而且寫入到process.env裏面 dotenvExpand(env) logger(path, env) } load(localPath) load(basePath) if (mode) { const defaultNodeEnv = (mode === 'production' || mode === 'test') ? mode : 'development' if (shouldForceDefaultEnv || process.env.NODE_ENV == null) { process.env.NODE_ENV = defaultNodeEnv } } }
2.加載用戶配置。讀取vue.config.js文件內部的配置shell
init (mode = process.env.VUE_CLI_MODE) { // 加載用戶配置 const userOptions = this.loadUserOptions() this.projectOptions = defaultsDeep(userOptions, defaults()) }
3.應用插件。前面初始化插件的時候(resolvePlugins方法)有一個apply屬性,調用這個方法就能夠導入對應的插件,這裏就是調用了serve.js的方法。
//const idToPlugin = id => ({ // id: id.replace(/^.\//, 'built-in:'), // apply: require(id) //}) init (mode = process.env.VUE_CLI_MODE) { // 應用插件 this.plugins.forEach(({ id, apply }) => { apply(new PluginAPI(id, this), this.projectOptions) }) // 從項目配置文件中應用webpack配置 if (this.projectOptions.chainWebpack) { this.webpackChainFns.push(this.projectOptions.chainWebpack) } if (this.projectOptions.configureWebpack) { this.webpackRawConfigFns.push(this.projectOptions.configureWebpack) } } }
4.註冊命令。在service註冊serve命令(前面run方法最後調用了fn函數,也就是registerCommand的第三個參數)
// serve.js api.registerCommand('serve', { description: 'start development server', usage: 'vue-cli-service serve [options] [entry]', options: {} }, async function serve (args) { // ... });
最終執行的serve.js 內註冊serve時傳遞的方法。webpack獲取到配置以後,實例化Compiler
傳遞給webpackDevServer
,經過webpackDevServer
實現自動編譯和熱更新。
// serve.js serve函數 async function serve (args) { //建立webpack編譯器 const compiler = webpack(webpackConfig) // compiler.run()便可完成一次編譯打包 // 建立本地服務 const server = new WebpackDevServer(compiler, Object.assign({ clientLogLevel: 'none', historyApiFallback: { disableDotRule: true, rewrites: [ { from: /./, to: path.posix.join(options.baseUrl, 'index.html') } ] }, contentBase: api.resolve('public'), watchContentBase: !isProduction, hot: !isProduction, quiet: true, compress: isProduction, publicPath: options.baseUrl, overlay: isProduction // TODO disable this ? false : { warnings: false, errors: true } }, projectDevServerOptions, { https: useHttps, proxy: proxySettings, })) return new Promise((resolve, reject) => { compiler.hooks.done.tap('vue-cli-service serve', stats => { // ... }) // ... server.listen(port, host, err => { if (err) { reject(err) } }) }) }
webpack
配置:api.resolveWebpackConfig()
devServer
配置webpack-dev-server
和 hot-reload(HRM)
中間件入口webpack-dev-server
實例啓動webpack-dev-server後,在目標文件夾中是看不到編譯後的文件的,實時編譯後的文件都保存到了內存當中。