vue-webpack-boilerplate分析
看完這篇文章你會學到通過vue-cli創建的項目,執行npm run dev後都發生了什麽事情以及執行流程。
在創建vue項目時,官方推薦使用vue-cli這個命令行工具。
# 全局安裝 vue-cli $ npm install --global vue-cli # 創建一個基於 webpack 模板的新項目 $ vue init webpack my-project # 安裝依賴,走你 $ cd my-project $ npm install $ npm run dev
執行npm run dev 會運行webpack。大致會編譯vue格式文件,熱加載,eslint語法檢測等功能。開發時過程帶給我們非常省時省力的體驗。
下面講下當執行npm run dev後都發生了什麽。
首先當通過vue-cli創建vue項目。執行的vue init webpack my-project
實際拷貝的是 https://github.com/vuejs-templates/webpack 中template提供的配置文件模版。他給我們提供了vue項目所需的架子並整合了webpack相關配置。
我們熟悉的build, src, config等目錄都裏面。
通過 package.json 可以看出有四個命令。
開發時要運行 npm run dev 或 npm run start 或 npm start
線上及生產環境要運行 npm run build
檢測JS語法運行 npm run lint
"scripts": { "dev": "node build/dev-server.js", "start": "node build/dev-server.js", "build": "node build/build.js", "lint": "eslint --ext .js,.vue src" },
npm run dev實際執行的就是 node build/dev-server.js
打開dev-server.js,第一行是 require(‘./check-versions‘)() 先分析這個文件
check-versions.js
大致就是將當前運行的node和npm環境與package.json中的engines中node和npm要求的最低版本進行對比,看是否滿足需求。
// 不錯的node模塊,使輸出內容帶前景色和背景色 var chalk = require(‘chalk‘) // 語義化版本,詳見http://semver.org/lang/zh-CN/ // https://www.npmjs.com/package/semver var semver = require(‘semver‘) var packageConfig = require(‘../package.json‘) // 顧名思義,提供了一些和shell命令名字功能都一樣的方法 var shell = require(‘shelljs‘) function exec (cmd) { return require(‘child_process‘).execSync(cmd).toString().trim() } var versionRequirements = [ { name: ‘node‘, // semver.clean(‘ =v1.2.3 ‘) => ‘1.2.3‘ currentVersion: semver.clean(process.version), versionRequirement: packageConfig.engines.node }, ] if (shell.which(‘npm‘)) { versionRequirements.push({ name: ‘npm‘, currentVersion: exec(‘npm --version‘), versionRequirement: packageConfig.engines.npm }) } module.exports = function () { var warnings = [] for (var i = 0; i < versionRequirements.length; i++) { var mod = versionRequirements[i] if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { warnings.push(mod.name + ‘: ‘ + chalk.red(mod.currentVersion) + ‘ should be ‘ + chalk.green(mod.versionRequirement) ) } } if (warnings.length) { console.log(‘‘) console.log(chalk.yellow(‘To use this template, you must update following to modules:‘)) console.log() for (var i = 0; i < warnings.length; i++) { var warning = warnings[i] console.log(‘ ‘ + warning) } console.log() process.exit(1) } }
打開package.json,你可以將
"engines": {
"node": ">= 4.0.0",
"npm": ">= 3.0.0"
},
改為
"engines": {
"node": ">= 4.0.0",
"npm": ">= 6.0.0"
},
然後運行 npm run dev 就會輸出類似的錯誤
config/index.js
接著是var config = require(‘../config‘)
這裏定義了一些開發環境和生產環境中不同的環境參數以及供後面的webpack配置文件使用的參數。
舉個例子,為什麽運行npm run dev會自動打開瀏覽器並且地址端口是8080。就是在這裏面定義的。
更詳細的參見:
https://github.com/vuejs-templates/webpack/blob/develop/docs/env.md
開發過程中可能需要調用後端的接口,如果要設置代理。
參見:
https://github.com/vuejs-templates/webpack/blob/develop/docs/proxy.md
本質上用到一個叫 http-proxy-middleware http代理中間件的node模塊
// see http://vuejs-templates.github.io/webpack for documentation. var path = require(‘path‘) module.exports = { build: { env: require(‘./prod.env‘), index: path.resolve(__dirname, ‘../dist/index.html‘), assetsRoot: path.resolve(__dirname, ‘../dist‘), assetsSubDirectory: ‘static‘, assetsPublicPath: ‘/‘, 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 productionGzip: false, 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 bundleAnalyzerReport: process.env.npm_config_report }, dev: { env: require(‘./dev.env‘), port: 8080, autoOpenBrowser: true, assetsSubDirectory: ‘static‘, assetsPublicPath: ‘/‘, 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: false } }
env.js
dev.env.js 和 prod.env.js 裏面放一些配置變量
比如我添加了一個KEY_ID
var merge = require(‘webpack-merge‘) var prodEnv = require(‘./prod.env‘) module.exports = merge(prodEnv, { NODE_ENV: ‘"development"‘, KEY_ID: ‘123456‘ })
在vue文件裏就可以用process.env.KEY_ID 輸出 123456
為什麽? 原理是用到了webpack提供的 DefinePlugin 插件
plugins: [ new webpack.DefinePlugin({ ‘process.env‘: config.dev.env }),
註意字符串要寫兩個引號。
回到dev-server.js,接著分析webpack配置文件。這類的文章很多了,簡要分析下
webpack.dev.conf
// 工具方法 var utils = require(‘./utils‘) var webpack = require(‘webpack‘) var config = require(‘../config‘) // webpack官方提供的一個webpack配置文件合並工具 var merge = require(‘webpack-merge‘) // 開發時和發布時公用的配置文件信息 var baseWebpackConfig = require(‘./webpack.base.conf‘) // 一個webpack插件,生成入口html文件,最強大的功能是動態自動插入webpack生成的css和js資源文件 // 一般的單頁面應用只有一個主html文件比如index.html,webpack打包生成js和css文件後, // 我們當然可以在手動寫標簽引入這些資源文件,但有時候生成後的文件帶有hash。如app-3d31retr.js。這時需要自動引入,並替換html。 var HtmlWebpackPlugin = require(‘html-webpack-plugin‘) // 不解釋,自行去github查文檔 var FriendlyErrorsPlugin = require(‘friendly-errors-webpack-plugin‘) // add hot-reload related code to entry chunks Object.keys(baseWebpackConfig.entry).forEach(function (name) { baseWebpackConfig.entry[name] = [‘./build/dev-client‘].concat(baseWebpackConfig.entry[name]) }) module.exports = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) }, // cheap-module-eval-source-map is faster for development devtool: ‘#cheap-module-eval-source-map‘, plugins: [ new webpack.DefinePlugin({ ‘process.env‘: config.dev.env }), // https://github.com/glenjamin/webpack-hot-middleware#installation--usage new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin(), // https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: ‘index.html‘, template: ‘index.html‘, inject: true }), new FriendlyErrorsPlugin() ] })
webpack.base.conf
主要講下resolve部分,webpack把各種類型資源的文件當做module來處理,並通過相應的loader編譯成js可運行的代碼。
在vueLoaderConfig裏已經定義了各種style loader了
resolve: { // 導入文件自動補全文件後綴 // 比如 src/index.js 中的 import App from ‘./App‘ // 完整的是import App from ‘./App.vue‘ extensions: [‘.js‘, ‘.vue‘, ‘.json‘], // 別名映射,引入模塊中可縮短字符串長度 // 如 import {playMode} from ‘common/js/config‘ 可能實際是 // import {playMode} from ‘../../common/js/config‘ alias: { ‘vue$‘: ‘vue/dist/vue.esm.js‘, ‘@‘: resolve(‘src‘), ‘common‘: resolve(‘src/common‘), ‘components‘: resolve(‘src/components‘), ‘base‘: resolve(‘src/base‘), ‘api‘: resolve(‘src/api‘) } }, module: { rules: [ // { // test: /\.(js|vue)$/, // loader: ‘eslint-loader‘, // enforce: ‘pre‘, // include: [resolve(‘src‘), resolve(‘test‘)], // options: { // // 友好提供eslint未通過原因及鏈接 // formatter: require(‘eslint-friendly-formatter‘) // } // }, { test: /\.vue$/, // https://vue-loader.vuejs.org/zh-cn/configurations/pre-processors.html loader: ‘vue-loader‘, // 最終結果類似, /* options: { loaders: { css: [‘vue-style-loader‘, { loader: ‘css-loader‘, options: { minimize: false, sourceMap: false } }], postcss: [‘vue-style-loader‘, { loader: ‘css-loader‘, options: { minimize: false, sourceMap: false } }], less: [‘vue-style-loader‘, { loader: ‘css-loader‘, options: { minimize: false, sourceMap: false } }, { loader: ‘less-loader‘, options: { sourceMap: false } }], sass: [‘vue-style-loader‘, { loader: ‘css-loader‘, options: { minimize: false, sourceMap: false } }, { loader: ‘sass-loader‘, options: { indentedSyntax: true, sourceMap: false } }], scss: [‘vue-style-loader‘, { loader: ‘css-loader‘, options: { minimize: false, sourceMap: false } }, { loader: ‘sass-loader‘, options: { sourceMap: false } }], stylus: [‘vue-style-loader‘, { loader: ‘css-loader‘, options: { minimize: false, sourceMap: false } }, { loader: ‘stylus-loader‘, options: { sourceMap: false } }], styl: [‘vue-style-loader‘, { loader: ‘css-loader‘, options: { minimize: false, sourceMap: false } }, { loader: ‘stylus-loader‘, options: { sourceMap: false } }] } } */ options: vueLoaderConfig },
為什麽在vue中的style中定義 lang="stylus" 或 lang="less" 就能直接寫相應的預處理語言啦。因為上面的webpack.base中已經把各種樣式loader都定義好啦
為什麽可以在vue類型類型裏用 <template>, <script> 和 <style>。這是vue-loader的功勞。當然也離不開webpack。
詳見:https://vue-loader.vuejs.org/zh-cn/start/spec.html
dev-server.js
// 檢測是否滿足版本需要 require(‘./check-versions‘)() // 引入外層config中的index.js,分為運行時和開發時的變量信息 // 我們可以自己添加一些變量 var config = require(‘../config‘) if (!process.env.NODE_ENV) { // 最終變成 process.env.NODE_ENV = JSON.parse(‘"development"‘) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) } // 第三方模塊。可調用系統默認的程序打開圖片,地址等,如opn(‘http://localhost:8080‘); open(‘./demo.jpg‘) var opn = require(‘opn‘) // node內置的核心模塊 var path = require(‘path‘) // node框架,在這裏用來啟動webserver var express = require(‘express‘) // 不解釋,核心編譯工具 // 官方文檔中不推薦全局安裝webpack,然後執行全局變量的方式 // 這裏在nodejs裏引入webpack,用提供的api來配置並啟動 var webpack = require(‘webpack‘) // http代理中間件,可轉發api等 var proxyMiddleware = require(‘http-proxy-middleware‘) var webpackConfig = require(‘./webpack.dev.conf‘)
簡單說
開發用的webpack配置文件是 webpack.dev.conf.js + webpack.base.conf.js合並後的結果
主要實現熱加載,友好的錯誤輸出,方便調試等功能
生產環境用到的是webpack.prod.conf.js + webpack.base.conf.js合並後的結果
主要實現打包壓縮css和js,不輸出警告和錯誤
參見:
https://vue-loader.vuejs.org/zh-cn/workflow/production.html
總結:多看官方文檔,多上github查資料。
https://github.com/vuejs-templates/webpack/blob/develop/docs/README.md
https://vue-loader.vuejs.org/zh-cn/
https://cn.vuejs.org/v2/guide/deployment.html
大致就是這些,有空再更。由於本人知識有限,文章有什麽不對的,還請斧正~~~
vue-webpack-boilerplate分析