前言:
vue腳手架指的是vue-cli它是vue官方提供的一個快速構建單頁面(SPA)環境配置的工具,cli 就是(command-line-interface ) 命令列界面 ,vue-cli是基于node環境利用webpack對檔案進行編譯、打包、壓縮、es6轉es5等一系列操作,目前vue-cli已經升級到了3.0版本,3.0所需的webpack版本是4.xxx,2.0版本目前也很流行,2.0所需的webpack版本是3.xxx,我們來講講兩者的配置:
Vue2.0:
一.安裝node.js環境:
去node官網下載node.js并安裝(http://nodejs.cn/download/),安裝完成后可以在終端執行 node -v查看node 是否安裝成功,下圖是安裝成功的提示,顯示出當前安裝的node的版本號,

二.全域安裝webpack:
為了在其他任何目錄下都能使用到webpack,進行全域安裝,執行npm install [email protected] -g 命令,npm 是Node集成的工具 npm install 是從github上下載webpack最新的包,“@3.12.0”表示安裝指定的版本,因為現在已經升級到了4.0版本,如果不指定版本版本號就會安裝最新的版本,同時vue-cli2需要的是3.xxx的版本,所以我們指定了一個固定版本,如果不指定則不需要,"-g" 全稱是 " global (全域) " 表示全域安裝,檢查是否安裝成功終端執行“webpack -v”或者"webpack --version",如果顯示具體的版本號則表示安裝成功,

三.全域安裝 vue-cli2:
執行“npm install @vue/cli -g”命令進行安裝,“npm install @vue/cli -g” 命令是腳手架3的,“npm install vue-cli -g”命令才是腳手架3的,腳手架2和腳手架3是不相同的,如果現在使用 “npm install vue-cli -g”命令進行安裝的話,下次使用腳手架3的時候就得卸載腳手架2,安裝腳手架3,為了減少不必要的操作我們執行 “npm install @vue/cli -g ” 命令進行安裝,然后再執行 “npm install @vue-cli-init -g ” 將腳手架2下載下來,在此環境下既可以安裝腳手架2的模板,有可以安裝腳手架3的模板, 檢查是否安裝成功終端執行“vue -V”或者"vue --version",如果顯示具體的版本號則表示安裝成功,具體安裝方式查看官網(https://cli.vuejs.org/zh/),

四.初始化專案:
進入到自己要安裝專案的檔案夾目錄,我這里是 “D:\webpackProject\vue-cli2> ” 執行 “vue init webpack vue-cli2-project ” 命令,出現如下圖提示 ,“vue-cli2-project” 是我們的專案檔案夾的名字,就是最終顯示在index.html中的title標簽里和package.json中的,也可以自己進行修改,我們一般不會去改,直接按回車鍵進行下一步,

“? Project description (A Vue.js project)” 是專案的描述,自己可以修改或者使用默認的,我們一般使用默認的直接按回車鍵進行下一步,

這里是作者的資訊,我們使用默認的,直接下一步,

這里有兩個選項:Runtime + Compiler 和Runtime-only ,Runtime-only要比Runtime + Compiler 輕大約6KB,而且效率要高, 按上下鍵可以進行選擇,默認是第一個,選擇好后按回車鍵進行下一步,

這一步是詢問是否使用vue-router(路由),因為在專案中我們會用到所以這里按Y 鍵,進行下一步,

這一步是詢問是否使用ESLint(語法檢查器),ES (ecscript) 即 javascript ,lint 限制的意思,也就是 javascript語法限制器,使得你的語法更加規范,如果你的語法不規范編輯器就會報錯,你可能在開發程序中因為一個空格導致語法不規范進而報錯(其實你的代碼沒有問題的),所以對于初學者不建議使用此語法,所以我們選擇 n,并進行下一步操作,

這一步是詢問是否使用單元測驗,這個用的人比較少,所以我們不適用,輸入n并進行一下步,

這一步詢問是否要進行e2e(端到端測驗),是一個自動化測驗的框架,這里我們就不使用了,直接輸入n,進行下一步:

這里詢問我們管理專案是用npm 還是yarn ,這里我們使用npm ,直接回車,接下來就是等待安裝node_modules,下圖表示安裝完成:

執行 cd vue-cli2-project 進入到我們的專案目錄下,然后執行 npm run dev 命令進行啟動我們的專案,下圖是我們的專案目錄:

五、 專案目錄介紹:
1、build 檔案夾:webpack的一些相關配置;
2、config 檔案夾:專案開發環境和生產環境的一些相關配置;
3、node_modules 檔案夾 :這里存放的是安裝包,比如webpack、第三方插件庫、專案的依賴檔案;
4、src 檔案夾:我們將要寫的代碼放在這里面,打包上線時會進行編譯、壓縮等操作,
5、static 檔案夾:這里存放的是一些靜態檔案比如圖片、css檔案、不需要進行壓縮的js檔案,打包時這個檔案夾將原封不動的放到dist(打包時自動生產的檔案夾)檔案夾下面,
6、.babelrc 檔案:ES6語法編譯配置,主要是將ES 轉成ES 需要適配那些瀏覽器
7、.editorconfig 檔案:定義代碼格式,對代碼的風格進行一個統一,
8、.gitignore 檔案:git上傳需要忽略的檔案格式
9、 .postcssrc.js 檔案:postcss組態檔
10、 index.html 檔案:要進行訪問的首頁面
11、package-lock.json 檔案:鎖定依賴模塊和子模塊的版本號
12、package.json 檔案:專案基本資訊,包依賴資訊等
13、README.md 檔案:專案說明檔案
檔案詳解:
1、package.json 檔案:當我們在命令列時 npm run dev 的時候程式執行的是package.json檔案的“script”腳本里的“dev”命令;

這段代碼的意思是啟動 “webpack-dev-server” 服務器,“--inline” 是 重新加載改變的部分,不會重繪頁面,--progress是啟動專案時顯示進度,“--config build/webpack.dev.conf.js” 是執行build下面的webpack.dev.conf.js組態檔,我們可以添加其他屬性比如 “--open” 是啟動專案后自動在瀏覽器打開專案,其它配置可以查看相關檔案(https://www.webpackjs.com/configuration/dev-server/#devserver),“start” 和“dev”的作用是一樣的,“build” 的作用是執行 build下的build.js檔案,將當前的專案進行打包,打包后生成一個dist檔案夾,放在其里面,webpack.dev.conf.js檔案是我們在開發環境下的webpack組態檔,打開次檔案,內容如下:

2.、build/webpack.dev.conf.js 檔案:
1 'use strict'
2 const utils = require('./utils') //引入的工具包
3 const webpack = require('webpack') //引入webpack包
4 const config = require('../config') //引入 config下的index.js檔案
5 const merge = require('webpack-merge') //合并組態檔
6 const path = require('path') //node的path模塊,對路徑進行處理
7 const baseWebpackConfig = require('./webpack.base.conf') //將生產和開發環境下共用的組態檔進行了抽離形成了改檔案
8 const CopyWebpackPlugin = require('copy-webpack-plugin') //拷貝插件
9 const HtmlWebpackPlugin = require('html-webpack-plugin') //加載html模塊
10 const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') //友好的錯誤提示插件
11 const portfinder = require('portfinder') //在當前機器上找一個可打開的埠號,默認是8080,如果埠號被占用則重新尋找可打開的埠號,
12
13 const HOST = process.env.HOST
14 const PORT = process.env.PORT && Number(process.env.PORT)
15
16 const devWebpackConfig = merge(baseWebpackConfig, { //利用merge插件將 baseWebpackConfig 配置與當前配置進行合并
17 module: {
18 rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) //引入utils中一些css-loader和postcss-loader
19 },
20
21 devtool: config.dev.devtool, //控制是否生成以及如何生成原始碼映射,這里引入的是config下的index.js的 “devtool: 'cheap-module-eval-source-map'”,
22
23 // these devServer options should be customized in /config/index.js
24 // dev-server的配置
25 devServer: {
26 clientLogLevel: 'warning', //當使用inline mode,devTools的命令列中將會顯示一些除錯資訊
27 historyApiFallback: { //當使用 HTML5 History API 時,任意的 404 回應都可能需要被替代為 index.html
28 rewrites: [
29 { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
30 ],
31 },
32 hot: true, //啟用 webpack 的模塊熱替換特性
33 contentBase: false, // since we use CopyWebpackPlugin.
34 compress: true,
35 host: HOST || config.dev.host, //要開啟的域名,可在package.json中的“dev”命令中進行配置
36 port: PORT || config.dev.port, //要開啟的埠號,可在package.json中的“dev”命令中進行配置
37 open: config.dev.autoOpenBrowser,//是否自動在瀏覽器中打開,可在package.json中的“dev”命令中進行配置
38 overlay: config.dev.errorOverlay
39 ? { warnings: false, errors: true }
40 : false,
41 publicPath: config.dev.assetsPublicPath, //
42 proxy: config.dev.proxyTable, //當出現跨域時設定代理,這里引入了config下的index.js的配置
43 quiet: true, // necessary for FriendlyErrorsPlugin 啟用 quiet 后,除了初始啟動資訊之外的任何內容都不會被列印到控制臺,這也意味著來自 webpack 的錯誤或警告在控制臺不可見
44 watchOptions: {
45 poll: config.dev.poll,
46 }
47 },
48 plugins: [ //插件部分
49 new webpack.DefinePlugin({ //配置全域變數
50 'process.env': require('../config/dev.env')
51 }),
52 new webpack.HotModuleReplacementPlugin(), // 模塊熱替換它允許在運行時更新各種模塊,而無需進行完全重繪
53 new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
54 new webpack.NoEmitOnErrorsPlugin(), // 這個插件的作用是在熱加載時直接回傳更新檔案名,而不是檔案的id,
55 // https://github.com/ampedandwired/html-webpack-plugin
56 new HtmlWebpackPlugin({ //打包時生成index.html并且自動加載app.js檔案 <!-- built files will be auto injected -->
57 filename: 'index.html',
58 template: 'index.html',
59 inject: true
60 }),
61 // copy custom static assets
62 new CopyWebpackPlugin([
63 {
64 from: path.resolve(__dirname, '../static'), //將static整個檔案夾原封不動地拷貝到dist目錄下,
65 to: config.dev.assetsSubDirectory,
66 ignore: ['.*']
67 }
68 ])
69 ]
70 })
71
72 module.exports = new Promise((resolve, reject) => {
73 portfinder.basePort = process.env.PORT || config.dev.port //獲取當前的埠號
74 portfinder.getPort((err, port) => {
75 if (err) {
76 reject(err)
77 } else {
78 // publish the new Port, necessary for e2e tests
79 process.env.PORT = port
80 // add port to devServer config
81 devWebpackConfig.devServer.port = port
82
83 // Add FriendlyErrorsPlugin
84 devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
85 compilationSuccessInfo: {
86 messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
87 },
88 one rrors: config.dev.notifyOnErrors
89 ? utils.createNotifierCallback()
90 : undefined
91 }))
92
93 resolve(devWebpackConfig)
94 }
95 })
96 })
本檔案 的核心就是將webpack.base.conf.js的配置(公共配置)與本檔案配置進行合并,再看一下 webpack.base.conf.js 檔案:
3、build/webpack.base.conf.js 檔案
1 'use strict'
2 const path = require('path') //node的path模塊,對路徑進行處理
3 const utils = require('./utils') //引入的工具包
4 const config = require('../config')//引入 config下的index.js檔案
5 const vueLoaderConfig = require('./vue-loader.conf') //根據NODE_ENV這個變數分析是否是生產環境,然后根據不同的環境來加載,判斷是否開啟了sourceMap的功能
6
7 function resolve (dir) {
8 return path.join(__dirname, '..', dir) //對路徑進行處理,獲取到絕對路徑
9 }
10
11
12
13 module.exports = {
14 context: path.resolve(__dirname, '../'), //對路徑進行處理,跳到當前專案的根目錄下
15 entry: { //入口檔案,即專案要引入哪個js檔案
16 app: './src/main.js' //因為 context 中已經跳到了當前專案的根目錄下,所以這里的路徑是以 ./src 開頭
17 },
18 output: { //輸出檔案,即專案要輸出到哪里去
19 path: config.build.assetsRoot, //輸出到根目錄下的dist問價夾里,具體地址可以在config下的index.js中進行修改
20 filename: '[name].js', //以檔案的原始名輸出
21 publicPath: process.env.NODE_ENV === 'production' //根據process.env.NODE_ENV 來判斷是生產模式還是開發模式,將最終打包的專案要放到服務器的什么地方,默認是 '/' 即服務器的根目錄下,
22 ? config.build.assetsPublicPath
23 : config.dev.assetsPublicPath
24 },
25 resolve: {
26 extensions: ['.js', '.vue', '.json'], //簡化一些檔案名,引入檔案時可以不帶后綴名
27 alias: {
28 'vue$': 'vue/dist/vue.esm.js',
29 '@': resolve('src'), //簡化檔案的引入問題,如:本檔案中要引入 src下的common里的demo.js,你就可以這樣引入:@/common/demo.js
30 }
31 },
32 module: {
33
34 rules: [
35 // 配置各種loader,來處理對應的檔案
36 {
37 test: /\.vue$/, //使用vue-loader處理以.vue結束的檔案
38 loader: 'vue-loader',
39 options: vueLoaderConfig
40 },
41 {
42 test: /\.js$/, //使用babel-loader處理以.js結束的檔案,即js檔案
43 loader: 'babel-loader',
44 include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
45 },
46 {
47 test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, //使用url-loader處理各種格式的圖片資源,最大限制10000KB,這里不處理src同級目錄下的static里的圖片,
48 loader: 'url-loader',
49 options: {
50 limit: 10000,
51 name: utils.assetsPath('img/[name].[hash:7].[ext]') //將處理后的放在img檔案下,并且加上7位hash值,
52 }
53 },
54 {
55 test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //使用url-loader處理視頻檔案,
56 loader: 'url-loader',
57 options: {
58 limit: 10000,
59 name: utils.assetsPath('media/[name].[hash:7].[ext]')
60 }
61 },
62 {
63 test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, //使用url-loader處理字體檔案,
64 loader: 'url-loader',
65 options: {
66 limit: 10000,
67 name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
68 }
69 }
70 ]
71 },
72 node: {
73 // prevent webpack from injecting useless setImmediate polyfill because Vue
74 // source contains it (although only uses it if it's native).
75 setImmediate: false,
76 // prevent webpack from injecting mocks to Node native modules
77 // that does not make sense for the client
78 dgram: 'empty',
79 fs: 'empty',
80 net: 'empty',
81 tls: 'empty',
82 child_process: 'empty'
83 }
84 }
主要的說明已經注釋在了檔案中,這個問價的主要配置有entry(入口檔案)、output(輸出檔案)、loader ,這些都是必備的,而一些plugins(插件)已經在對應的環境檔案(webpack.dev.config.js、webpack.prod.config.js)中進行了配置,再看一下webpack.prod.config.js檔案:
4、build/webpack.prod.config.js:
1 'use strict'
2 const path = require('path')
3 const utils = require('./utils')
4 const webpack = require('webpack')
5 const config = require('../config')
6 const merge = require('webpack-merge')
7 const baseWebpackConfig = require('./webpack.base.conf')
8 const CopyWebpackPlugin = require('copy-webpack-plugin')
9 const HtmlWebpackPlugin = require('html-webpack-plugin')
10 const ExtractTextPlugin = require('extract-text-webpack-plugin') //抽離css樣式,防止將樣式打包在js中引起頁面樣式加載錯亂的現象
11 const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')//主要是用來壓縮css檔案
12 const UglifyJsPlugin = require('uglifyjs-webpack-plugin') //對js檔案進行壓縮
13
14 const env = require('../config/prod.env')
15
16 const webpackConfig = merge(baseWebpackConfig, {
17 module: {
18 rules: utils.styleLoaders({
19 sourceMap: config.build.productionSourceMap,
20 extract: true,
21 usePostCSS: true
22 })
23 },
24 devtool: config.build.productionSourceMap ? config.build.devtool : false,
25 output: {
26 path: config.build.assetsRoot,
27 filename: utils.assetsPath('js/[name].[chunkhash].js'),
28 chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
29 },
30 plugins: [
31 // http://vuejs.github.io/vue-loader/en/workflow/production.html
32 new webpack.DefinePlugin({
33 'process.env': env
34 }),
35 new UglifyJsPlugin({
36 uglifyOptions: { //配置項
37 compress: {
38 warnings: false
39 }
40 },
41 sourceMap: config.build.productionSourceMap, //使用sourceMap將錯誤訊息位置映射到模塊(這會減慢編譯速度),
42 parallel: true //啟用/禁用多行程并行運行,啟用后會提高構建速度
43 }),
44
45 new ExtractTextPlugin({
46 filename: utils.assetsPath('css/[name].[contenthash].css'),
47
48 allChunks: true,
49 }),
50 // Compress extracted CSS. We are using this plugin so that possible
51 // duplicated CSS from different components can be deduped.
52 new OptimizeCSSPlugin({
53 cssProcessorOptions: config.build.productionSourceMap
54 ? { safe: true, map: { inline: false } } //判斷是否生成行內映射,如果生成則會生成一個source-map檔案
55 : { safe: true }
56 }),
57 // generate dist index.html with correct asset hash for caching.
58 // you can customize output by editing /index.html
59 // see https://github.com/ampedandwired/html-webpack-plugin
60 new HtmlWebpackPlugin({
61 filename: config.build.index, //將會生成一個index.html檔案,放到dist檔案下
62 template: 'index.html',
63 inject: true, //將所有js資源放在body標簽的底部
64 minify: { //控制是否進行壓縮
65 removeComments: true, //洗掉所有的注釋
66 collapseWhitespace: true, //折疊構成檔案樹中文本節點的空白
67 removeAttributeQuotes: true //盡可能洗掉屬性周圍的引號
68 // more options:
69 // https://github.com/kangax/html-minifier#options-quick-reference
70 },
71 // necessary to consistently work with multiple chunks via CommonsChunkPlugin
72 chunksSortMode: 'dependency' //允許控制塊在包含到HTML之前按照依賴排序
73 }),
74 // keep module.id stable when vendor modules does not change
75 new webpack.HashedModuleIdsPlugin(), //該插件會根據模塊的相對路徑生成一個四位數的hash作為模塊id, 建議用于生產環境,
76 // enable scope hoisting
77 new webpack.optimize.ModuleConcatenationPlugin(),//啟用作用域提升,讓代碼檔案更小、運行的更快
78 // split vendor js into its own file
79 new webpack.optimize.CommonsChunkPlugin({ //主要是用來提取第三方庫和公共模塊,避免首屏加載的bundle檔案或者按需加載的bundle檔案體積過大,從而導致加載時間過長
80 name: 'vendor',
81 minChunks (module) {
82 // any required modules inside node_modules are extracted to vendor
83 return (
84 module.resource &&
85 /\.js$/.test(module.resource) &&
86 module.resource.indexOf(
87 path.join(__dirname, '../node_modules')
88 ) === 0
89 )
90 }
91 }),
92 // extract webpack runtime and module manifest to its own file in order to
93 // prevent vendor hash from being updated whenever app bundle is updated
94 new webpack.optimize.CommonsChunkPlugin({
95 name: 'manifest',
96 minChunks: Infinity
97 }),
98 // This instance extracts shared chunks from code splitted chunks and bundles them
99 // in a separate chunk, similar to the vendor chunk
100 // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
101 new webpack.optimize.CommonsChunkPlugin({
102 name: 'app',
103 async: 'vendor-async',
104 children: true,
105 minChunks: 3
106 }),
107
108 // copy custom static assets
109 new CopyWebpackPlugin([ //復制模塊
110 {
111 from: path.resolve(__dirname, '../static'),
112 to: config.build.assetsSubDirectory,
113 ignore: ['.*']
114 }
115 ])
116 ]
117 })
118
119 if (config.build.productionGzip) {
120 const CompressionWebpackPlugin = require('compression-webpack-plugin')
121
122 webpackConfig.plugins.push(
123 new CompressionWebpackPlugin({
124 asset: '[path].gz[query]',
125 algorithm: 'gzip',
126 test: new RegExp(
127 '\\.(' +
128 config.build.productionGzipExtensions.join('|') +
129 ')$'
130 ),
131 threshold: 10240,
132 minRatio: 0.8
133 })
134 )
135 }
136
137 if (config.build.bundleAnalyzerReport) {
138 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
139 webpackConfig.plugins.push(new BundleAnalyzerPlugin())
140 }
141
142 module.exports = webpackConfig
當我們執行 npm run build 打包時執行的是: build下的build.js檔案,build.js中引入了webpack.prod.config.js,因此build.js才是生產環境所需的webpack檔案,

5、build/build.js:
1 'use strict'
2 require('./check-versions')() //該檔案用于檢測node和npm的版本,實作版本依賴
3
4 process.env.NODE_ENV = 'production'
5
6 const ora = require('ora') //在node端加載影片模塊
7 const rm = require('rimraf') //用來洗掉檔案和檔案夾的
8 const path = require('path')
9 const chalk = require('chalk') //修改控制臺中字串的樣式
10 const webpack = require('webpack')
11 const config = require('../config')
12 const webpackConfig = require('./webpack.prod.conf')
13
14 const spinner = ora('building for production...') //設定一個影片的內容為 "building for production..."
15 spinner.start() //加載影片
16
17 rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { //利用 rm 模塊先洗掉dist檔案再生成新檔案,因為有時候會使用hash來命名,洗掉整個檔案可避免冗余
18 if (err) throw err
19 webpack(webpackConfig, (err, stats) => { //將一下配置內容與 webpack.prod.conf.js中的配置進行合并
20 spinner.stop() //停止影片
21 if (err) throw err
22 process.stdout.write(stats.toString({
23 colors: true,
24 modules: false,
25 children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
26 chunks: false,
27 chunkModules: false
28 }) + '\n\n')
29
30 if (stats.hasErrors()) {
31 console.log(chalk.red(' Build failed with errors.\n'))
32 process.exit(1)
33 }
34
35 console.log(chalk.cyan(' Build complete.\n'))
36 console.log(chalk.yellow(
37 ' Tip: built files are meant to be served over an HTTP server.\n' +
38 ' Opening index.html over file:// won\'t work.\n'
39 ))
40 })
41 })
6、build/check-versions.js: 檢測node和npm的版本,實作版本依賴
1 'use strict'
2 // 該檔案用于檢測node和npm的版本,實作版本依賴
3 const chalk = require('chalk') //node.js中的模塊,作用是修改控制臺中字串的樣式
4 const semver = require('semver') //node.js中的模塊,對版本進行檢查
5 const packageConfig = require('../package.json') //引入page.json檔案
6 const shell = require('shelljs')
7
8 function exec (cmd) {
9 //通過child_process模塊的新建子行程,執行 Unix 系統命令后轉成沒有空格的字串
10 return require('child_process').execSync(cmd).toString().trim()
11 }
12
13 const versionRequirements = [
14 {
15 name: 'node',
16 currentVersion: semver.clean(process.version), //使用semver格式化版本
17 versionRequirement: packageConfig.engines.node //獲取package.json中設定的node版本
18 }
19 ]
20
21 if (shell.which('npm')) {
22 versionRequirements.push({
23 name: 'npm',
24 currentVersion: exec('npm --version'), //自動呼叫npm --version命令,并且把引數回傳給exec函式,從而獲取純凈的版本號
25 versionRequirement: packageConfig.engines.npm
26 })
27 }
28
29 module.exports = function () {
30 const warnings = []
31
32 for (let i = 0; i < versionRequirements.length; i++) {
33 const mod = versionRequirements[i]
34
35 if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
36 //如果上面的版本號不符合package.json檔案中指定的版本號,就執行下面錯誤提示的代碼
37 warnings.push(mod.name + ': ' +
38 chalk.red(mod.currentVersion) + ' should be ' +
39 chalk.green(mod.versionRequirement)
40 )
41 }
42 }
43
44 if (warnings.length) {
45 console.log('')
46 console.log(chalk.yellow('To use this template, you must update following to modules:'))
47 console.log()
48
49 for (let i = 0; i < warnings.length; i++) {
50 const warning = warnings[i]
51 console.log(' ' + warning)
52 }
53
54 console.log()
55 process.exit(1)
56 }
57 }
7、build/vue-loader.conf.js:
1 'use strict'
2
3 //根據NODE_ENV這個變數分析是否是生產環境,然后根據不同的環境來加載,判斷是否開啟了sourceMap的功能,方便之后在cssLoaders中加上sourceMap功能,然后判斷是否設定了cacheBusting屬性,
4 // 它指的是快取破壞,特別是進行sourceMap debug時,設定成false是非常有幫助的,最后就是一個轉化請求的內容,video、source、img、image等的屬性進行配置,具體的還是需要去了解vue-loader這個
5 // webpack的loader加載器
6
7 const utils = require('./utils')
8 const config = require('../config')
9 const isProduction = process.env.NODE_ENV === 'production'
10 const sourceMapEnabled = isProduction
11 ? config.build.productionSourceMap
12 : config.dev.cssSourceMap
13 //處理專案中的css檔案,生產環境和測驗環境默認是打開sourceMap,而extract中的提取樣式到單獨檔案只有在生產環境中才需要
14 module.exports = {
15 loaders: utils.cssLoaders({
16 sourceMap: sourceMapEnabled,
17 extract: isProduction
18 }),
19 cssSourceMap: sourceMapEnabled,
20 cacheBusting: config.dev.cacheBusting,
21 transformToRequire: {//在模版編譯程序中,編譯器可以將某些屬性,如 src 路徑,轉換為require呼叫,以便目標資源可以由 webpack 處理.
22 video: ['src', 'poster'],
23 source: 'src',
24 img: 'src',
25 image: 'xlink:href'
26 }
27 }
8、build/utils:
1 'use strict'
2 const path = require('path')
3 const config = require('../config')
4 const ExtractTextPlugin = require('extract-text-webpack-plugin') ////抽離css樣式,防止將樣式打包在js中引起頁面樣式加載錯亂的現象
5 const packageConfig = require('../package.json')
6
7
8 //匯出檔案的位置,根據環境判斷開發環境和生產環境,為config檔案中index.js檔案中定義的build.assetsSubDirectory或
9 exports.assetsPath = function (_path) {
10 const assetsSubDirectory = process.env.NODE_ENV === 'production'
11 ? config.build.assetsSubDirectory
12 : config.dev.assetsSubDirectory
13
14 return path.posix.join(assetsSubDirectory, _path)
15 }
16
17 //使用了css-loader和postcssLoader,通過options.usePostCSS屬性來判斷是否使用postcssLoader中壓縮等方法
18 exports.cssLoaders = function (options) { //匯出css-loader
19 options = options || {}
20
21 const cssLoader = {
22 loader: 'css-loader',
23 options: {
24 sourceMap: options.sourceMap
25 }
26 }
27
28 const postcssLoader = {
29 loader: 'postcss-loader',
30 options: {
31 sourceMap: options.sourceMap
32 }
33 }
34
35 // generate loader string to be used with extract text plugin
36 function generateLoaders (loader, loaderOptions) {
37 const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] //根據傳入的引數判斷是使用cssLoader、 postcssLoader還是只使用 cssLoader
38
39 if (loader) {
40 loaders.push({
41 loader: loader + '-loader',
42 options: Object.assign({}, loaderOptions, { //將后面的兩個物件合并后再進行復制
43 sourceMap: options.sourceMap
44 })
45 })
46 }
47
48 // Extract CSS when that option is specified
49 // (which is the case during production build)
50 if (options.extract) {
51 return ExtractTextPlugin.extract({
52 use: loaders,
53 fallback: 'vue-style-loader'
54 })
55 } else {
56 return ['vue-style-loader'].concat(loaders)
57 }
58 }
59
60 // https://vue-loader.vuejs.org/en/configurations/extract-css.html
61 return {
62 css: generateLoaders(),
63 postcss: generateLoaders(),
64 less: generateLoaders('less'),
65 sass: generateLoaders('sass', { indentedSyntax: true }),
66 scss: generateLoaders('sass'),
67 stylus: generateLoaders('stylus'),
68 styl: generateLoaders('stylus')
69 }
70 }
71
72 // Generate loaders for standalone style files (outside of .vue)
73 exports.styleLoaders = function (options) {
74 const output = []
75 const loaders = exports.cssLoaders(options)
76
77 for (const extension in loaders) {
78 const loader = loaders[extension]
79 output.push({
80 test: new RegExp('\\.' + extension + '$'),
81 use: loader
82 })
83 }
84
85 return output
86 }
87
88 exports.createNotifierCallback = () => {
89 const notifier = require('node-notifier')
90
91 return (severity, errors) => {
92 if (severity !== 'error') return
93
94 const error = errors[0]
95 const filename = error.file && error.file.split('!').pop()
96
97 notifier.notify({
98 title: packageConfig.name,
99 message: severity + ': ' + error.name,
100 subtitle: filename || '',
101 icon: path.join(__dirname, 'logo.png')
102 })
103 }
104 }
9、config/index.js: 生產 和 開發 環境下webpack的公共組態檔
1 const path = require('path')
2
3 module.exports = {
4 dev: { //開發環境下的配置
5
6 // Paths
7 assetsSubDirectory: 'static', //子目錄,一般存放css,js,image等檔案
8 assetsPublicPath: '/', //根目錄
9 proxyTable: {}, //在這里使用代理解決跨越問題
10
11 // Various Dev Server settings
12 host: 'localhost', // 域名
13 port: 8080, // 開啟的埠號,默認是8080
14 autoOpenBrowser: true, //是否自動打開瀏覽器
15 errorOverlay: true, //瀏覽器錯誤提示
16 notifyOnErrors: true, //跨平臺錯誤提示
17 poll: false, // 使用檔案系統(file system)獲取檔案改動的通知devServer.watchOptions
18
19
20 /**
21 * Source Maps
22 */
23
24 // https://webpack.js.org/configuration/devtool/#development
25 devtool: 'cheap-module-eval-source-map',//增加除錯,該屬性為原始源代碼(僅限行)不可在生產環境中使用
26
27 cacheBusting: true,//使快取失效
28
29 cssSourceMap: true //代碼壓縮后進行調bug定位將非常困難,于是引入sourcemap記錄壓縮前后的位置資訊記錄,當產生錯誤時直接定位到未壓縮前的位置,將大大的方便我們除錯
30 },
31
32 build: { //生產發環境下的配置
33 // Template for index.html
34 index: path.resolve(__dirname, '../dist/index.html'), //index.html編譯后生成的位置和名字
35
36 // Paths
37 assetsRoot: path.resolve(__dirname, '../dist'),//編譯后存放生成環境代碼的位置
38 assetsSubDirectory: 'static', //js,css,images存放檔案夾名
39 assetsPublicPath: '/', //發布的根目錄,通常本地打包dist后打開檔案會報錯,此處修改為./,如果是上線的檔案,可根據檔案存放位置進行更改路徑
40
41 productionSourceMap: true,
42
43 devtool: '#source-map',
44
45 productionGzip: false,
46 productionGzipExtensions: ['js', 'css'],
47
48 bundleAnalyzerReport: process.env.npm_config_report
49 }
50 }
10、config/dev.env.js:
1 'use strict'
2 // 當在開發環境下參考(webpack.dev.config.js中的plugin中)的是此檔案,次檔案指定了 開發模式: node-env ,
3 //利用merge方法將prod.env.js與本檔案進行合并,在開發模式下輸出 NODE_ENV="development"
4
5 //webpack.dev.config.js中的plugin參考如下:
6 // new webpack.DefinePlugin({
7 // 'process.env': require('../config/dev.env')
8 // })
9 const merge = require('webpack-merge')
10 const prodEnv = require('./prod.env')
11 module.exports = merge(prodEnv, {
12 NODE_ENV: '"development"'
13 })
11、config/prod.env.js:
1 'use strict'
2 // 在生產模式下呼叫此檔案
3 // 在webpack.prod.config.js中的plugin中參考如下:
4 //const env = require('../config/prod.env')
5 // new webpack.DefinePlugin({
6 // 'process.env': env
7 // }),
8 module.exports = {
9 NODE_ENV: '"production"'
10 }
12 、node_modules檔案夾:該檔案夾下存放的是node的一些依賴模塊,比如:require模塊、path模塊、http-proxy-middleware模塊,還有一些我們通過npm安裝的插件模塊,比如vue、md5、vue-cli、ivew等,
13.、src檔案夾: 該檔案夾下面存放的是我們專案代碼以及一些檔案,components檔案夾存放了我們自己寫的組件,router檔案夾里面存放了路由配置,mian.js是整個專案的入口js,在build檔案夾下的webpack.dev.config.js中的entry中有配置(
app: './src/main.js'),App.vue檔案是專案的首頁面, vue 3.0: 1.安裝vue3:新建一個檔案夾,進入該檔案夾下,執行 vue create ( 專案名稱) , 如下圖:
vuecli3為專案名稱,進入下一步,
上面提示:請選擇一個配置,下面有3個選項,第一個 “myset ” 是 我自己手動選擇的配置,你們第一個安裝你沒有這個選項,如果選擇了第3個通過手動選擇后,下次再安裝時會出現在這里,第二個 “default”是默認的,第3個是 手動選擇,我們先選擇第3個,進入下一步,
這里要我們選擇一個配置,按住上下鍵進行調轉,空格鍵進行選中或者取消,
這一步詢問的是 把專案的組態檔放在獨立的組態檔中還是放在package.json檔案中,這里我們選擇第一個,方便我們以后的修改,進行下一步
這里詢問的是我們手動配置要不要進行保存,以便下次使用,也就是安裝第一步的時候的選擇,輸入 y,
這里要輸入的是要保存的命字,進行一下步安裝,
安裝完成,
啟動專案:
這是vue3的專案結構,顯然和vue2的結構不一樣,沒有了config檔案夾而且還多了一個.git檔案,方便我們專案管理,其中public相當于vue2中的static靜態檔案夾,相同檔案我就不說了,我只說一下不同檔案,
我們先看一下package.json檔案,
開發依賴少了很多,因為vue3.0講究的是 0 配置,因為不顯示的這些檔案不需要我們去改,我們通過npm安裝的依賴會存在哪里呢?
這里我安裝了2個依賴,很顯然是放在package.json檔案下的,方便我們去管理自己的依賴,那默認的那些依賴存在哪里呢?
,其實是通過 "@vue/cli-service": "^4.0.0",去管理我們的依賴的,在 “node_modules” => “@vue” => cli-service => package.json,這里面就是隱藏的依賴,
vue2中的config檔案夾隱藏到了“node_modules” => “@vue” => cli-service => webpack.config.js中,而在webpack.config.js中有這一行代碼:
所以再找到Service.js檔案,
1 const fs = require('fs')
2 const path = require('path')
3 const debug = require('debug')
4 const chalk = require('chalk')
5 const readPkg = require('read-pkg')
6 const merge = require('webpack-merge')
7 const Config = require('webpack-chain')
8 const PluginAPI = require('./PluginAPI')
9 const dotenv = require('dotenv')
10 const dotenvExpand = require('dotenv-expand')
11 const defaultsDeep = require('lodash.defaultsdeep')
12 const { warn, error, isPlugin, resolvePluginId, loadModule } = require('@vue/cli-shared-utils')
13
14 const { defaults, validate } = require('./options')
15
16 module.exports = class Service {
17 constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
18 process.VUE_CLI_SERVICE = this
19 this.initialized = false
20 this.context = context
21 this.inlineOptions = inlineOptions
22 this.webpackChainFns = []
23 this.webpackRawConfigFns = []
24 this.devServerConfigFns = []
25 this.commands = {}
26 // Folder containing the target package.json for plugins
27 this.pkgContext = context
28 // package.json containing the plugins
29 this.pkg = this.resolvePkg(pkg)
30 // If there are inline plugins, they will be used instead of those
31 // found in package.json.
32 // When useBuiltIn === false, built-in plugins are disabled. This is mostly
33 // for testing.
34 this.plugins = this.resolvePlugins(plugins, useBuiltIn)
35 // pluginsToSkip will be populated during run()
36 this.pluginsToSkip = new Set()
37 // resolve the default mode to use for each command
38 // this is provided by plugins as module.exports.defaultModes
39 // so we can get the information without actually applying the plugin.
40 this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => {
41 return Object.assign(modes, defaultModes)
42 }, {})
43 }
44
45 resolvePkg (inlinePkg, context = this.context) {
46 if (inlinePkg) {
47 return inlinePkg
48 } else if (fs.existsSync(path.join(context, 'package.json'))) {
49 const pkg = readPkg.sync({ cwd: context })
50 if (pkg.vuePlugins && pkg.vuePlugins.resolveFrom) {
51 this.pkgContext = path.resolve(context, pkg.vuePlugins.resolveFrom)
52 return this.resolvePkg(null, this.pkgContext)
53 }
54 return pkg
55 } else {
56 return {}
57 }
58 }
59
60 init (mode = process.env.VUE_CLI_MODE) {
61 if (this.initialized) {
62 return
63 }
64 this.initialized = true
65 this.mode = mode
66
67 // load mode .env
68 if (mode) {
69 this.loadEnv(mode)
70 }
71 // load base .env
72 this.loadEnv()
73
74 // load user config
75 const userOptions = this.loadUserOptions()
76 this.projectOptions = defaultsDeep(userOptions, defaults())
77
78 debug('vue:project-config')(this.projectOptions)
79
80 // apply plugins.
81 this.plugins.forEach(({ id, apply }) => {
82 if (this.pluginsToSkip.has(id)) return
83 apply(new PluginAPI(id, this), this.projectOptions)
84 })
85
86 // apply webpack configs from project config file
87 if (this.projectOptions.chainWebpack) {
88 this.webpackChainFns.push(this.projectOptions.chainWebpack)
89 }
90 if (this.projectOptions.configureWebpack) {
91 this.webpackRawConfigFns.push(this.projectOptions.configureWebpack)
92 }
93 }
94
95 loadEnv (mode) {
96 const logger = debug('vue:env')
97 const basePath = path.resolve(this.context, `.env${mode ? `.${mode}` : ``}`)
98 const localPath = `${basePath}.local`
99
100 const load = envPath => {
101 try {
102 const env = dotenv.config({ path: envPath, debug: process.env.DEBUG })
103 dotenvExpand(env)
104 logger(envPath, env)
105 } catch (err) {
106 // only ignore error if file is not found
107 if (err.toString().indexOf('ENOENT') < 0) {
108 error(err)
109 }
110 }
111 }
112
113 load(localPath)
114 load(basePath)
115
116 // by default, NODE_ENV and BABEL_ENV are set to "development" unless mode
117 // is production or test. However the value in .env files will take higher
118 // priority.
119 if (mode) {
120 // always set NODE_ENV during tests
121 // as that is necessary for tests to not be affected by each other
122 const shouldForceDefaultEnv = (
123 process.env.VUE_CLI_TEST &&
124 !process.env.VUE_CLI_TEST_TESTING_ENV
125 )
126 const defaultNodeEnv = (mode === 'production' || mode === 'test')
127 ? mode
128 : 'development'
129 if (shouldForceDefaultEnv || process.env.NODE_ENV == null) {
130 process.env.NODE_ENV = defaultNodeEnv
131 }
132 if (shouldForceDefaultEnv || process.env.BABEL_ENV == null) {
133 process.env.BABEL_ENV = defaultNodeEnv
134 }
135 }
136 }
137
138 setPluginsToSkip (args) {
139 const skipPlugins = args['skip-plugins']
140 const pluginsToSkip = skipPlugins
141 ? new Set(skipPlugins.split(',').map(id => resolvePluginId(id)))
142 : new Set()
143
144 this.pluginsToSkip = pluginsToSkip
145 }
146
147 resolvePlugins (inlinePlugins, useBuiltIn) {
148 const idToPlugin = id => ({
149 id: id.replace(/^.\//, 'built-in:'),
150 apply: require(id)
151 })
152
153 let plugins
154
155 const builtInPlugins = [
156 './commands/serve',
157 './commands/build',
158 './commands/inspect',
159 './commands/help',
160 // config plugins are order sensitive
161 './config/base',
162 './config/css',
163 './config/prod',
164 './config/app'
165 ].map(idToPlugin)
166
167 if (inlinePlugins) {
168 plugins = useBuiltIn !== false
169 ? builtInPlugins.concat(inlinePlugins)
170 : inlinePlugins
171 } else {
172 const projectPlugins = Object.keys(this.pkg.devDependencies || {})
173 .concat(Object.keys(this.pkg.dependencies || {}))
174 .filter(isPlugin)
175 .map(id => {
176 if (
177 this.pkg.optionalDependencies &&
178 id in this.pkg.optionalDependencies
179 ) {
180 let apply = () => {}
181 try {
182 apply = require(id)
183 } catch (e) {
184 warn(`Optional dependency ${id} is not installed.`)
185 }
186
187 return { id, apply }
188 } else {
189 return idToPlugin(id)
190 }
191 })
192 plugins = builtInPlugins.concat(projectPlugins)
193 }
194
195 // Local plugins
196 if (this.pkg.vuePlugins && this.pkg.vuePlugins.service) {
197 const files = this.pkg.vuePlugins.service
198 if (!Array.isArray(files)) {
199 throw new Error(`Invalid type for option 'vuePlugins.service', expected 'array' but got ${typeof files}.`)
200 }
201 plugins = plugins.concat(files.map(file => ({
202 id: `local:${file}`,
203 apply: loadModule(`./${file}`, this.pkgContext)
204 })))
205 }
206
207 return plugins
208 }
209
210 async run (name, args = {}, rawArgv = []) {
211 // resolve mode
212 // prioritize inline --mode
213 // fallback to resolved default modes from plugins or development if --watch is defined
214 const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])
215
216 // --skip-plugins arg may have plugins that should be skipped during init()
217 this.setPluginsToSkip(args)
218
219 // load env variables, load user config, apply plugins
220 this.init(mode)
221
222 args._ = args._ || []
223 let command = this.commands[name]
224 if (!command && name) {
225 error(`command "${name}" does not exist.`)
226 process.exit(1)
227 }
228 if (!command || args.help || args.h) {
229 command = this.commands.help
230 } else {
231 args._.shift() // remove command itself
232 rawArgv.shift()
233 }
234 const { fn } = command
235 return fn(args, rawArgv)
236 }
237
238 resolveChainableWebpackConfig () {
239 const chainableConfig = new Config()
240 // apply chains
241 this.webpackChainFns.forEach(fn => fn(chainableConfig))
242 return chainableConfig
243 }
244
245 resolveWebpackConfig (chainableConfig = this.resolveChainableWebpackConfig()) {
246 if (!this.initialized) {
247 throw new Error('Service must call init() before calling resolveWebpackConfig().')
248 }
249 // get raw config
250 let config = chainableConfig.toConfig()
251 const original = config
252 // apply raw config fns
253 this.webpackRawConfigFns.forEach(fn => {
254 if (typeof fn === 'function') {
255 // function with optional return value
256 const res = fn(config)
257 if (res) config = merge(config, res)
258 } else if (fn) {
259 // merge literal values
260 config = merge(config, fn)
261 }
262 })
263
264 // #2206 If config is merged by merge-webpack, it discards the __ruleNames
265 // information injected by webpack-chain. Restore the info so that
266 // vue inspect works properly.
267 if (config !== original) {
268 cloneRuleNames(
269 config.module && config.module.rules,
270 original.module && original.module.rules
271 )
272 }
273
274 // check if the user has manually mutated output.publicPath
275 const target = process.env.VUE_CLI_BUILD_TARGET
276 if (
277 !process.env.VUE_CLI_TEST &&
278 (target && target !== 'app') &&
279 config.output.publicPath !== this.projectOptions.publicPath
280 ) {
281 throw new Error(
282 `Do not modify webpack output.publicPath directly. ` +
283 `Use the "publicPath" option in vue.config.js instead.`
284 )
285 }
286
287 if (typeof config.entry !== 'function') {
288 let entryFiles
289 if (typeof config.entry === 'string') {
290 entryFiles = [config.entry]
291 } else if (Array.isArray(config.entry)) {
292 entryFiles = config.entry
293 } else {
294 entryFiles = Object.values(config.entry || []).reduce((allEntries, curr) => {
295 return allEntries.concat(curr)
296 }, [])
297 }
298
299 entryFiles = entryFiles.map(file => path.resolve(this.context, file))
300 process.env.VUE_CLI_ENTRY_FILES = JSON.stringify(entryFiles)
301 }
302
303 return config
304 }
305
306 loadUserOptions () {
307 // vue.config.js
308 let fileConfig, pkgConfig, resolved, resolvedFrom
309 const configPath = (
310 process.env.VUE_CLI_SERVICE_CONFIG_PATH ||
311 path.resolve(this.context, 'vue.config.js')
312 )
313 if (fs.existsSync(configPath)) {
314 try {
315 fileConfig = require(configPath)
316
317 if (typeof fileConfig === 'function') {
318 fileConfig = fileConfig()
319 }
320
321 if (!fileConfig || typeof fileConfig !== 'object') {
322 error(
323 `Error loading ${chalk.bold('vue.config.js')}: should export an object or a function that returns object.`
324 )
325 fileConfig = null
326 }
327 } catch (e) {
328 error(`Error loading ${chalk.bold('vue.config.js')}:`)
329 throw e
330 }
331 }
332
333 // package.vue
334 pkgConfig = this.pkg.vue
335 if (pkgConfig && typeof pkgConfig !== 'object') {
336 error(
337 `Error loading vue-cli config in ${chalk.bold(`package.json`)}: ` +
338 `the "vue" field should be an object.`
339 )
340 pkgConfig = null
341 }
342
343 if (fileConfig) {
344 if (pkgConfig) {
345 warn(
346 `"vue" field in package.json ignored ` +
347 `due to presence of ${chalk.bold('vue.config.js')}.`
348 )
349 