構建vue項目的開發環境配置,官方提供了腳手架工具vue-cli來快速構建一個開發環境,初始化一個vue項目操做命令以下:css
// 安裝node.js,內含npm,Node.js官網:https://nodejs.org/en/ 。 // 設置npm鏡像cnpm命令行工具 npm install -g cnpm --registry=https://registry.npm.taobao.org // 全局安裝 vue-cli cnpm install -g vue-cli // 先建立並進入vue項目目錄 cd W:\Workspaces\git_repositories\javalsj-blog-vue // 建立一個基於 webpack 模板的新項目 vue init webpack javalsj-blog-vue // 先進入vue項目目錄下再安裝該項目的依賴 cd W:\Workspaces\git_repositories\javalsj-blog-vue\javalsj-blog-vue // 初始化安裝項目 cnpm install // 運行項目 npm run dev
打開項目文件目錄,以下:
html
下面介紹項目初始目錄結構信息,只是瞭解一下就行。前端
|-- build // 項目構建(webpack)相關代碼 | |-- build.js // 生產環境構建代碼 | |-- check-version.js // 檢查node、npm等版本 | |-- dev-client.js // 熱重載相關 | |-- dev-server.js // 構建本地服務器 | |-- utils.js // 構建工具相關 | |-- webpack.base.conf.js // webpack基礎配置 | |-- webpack.dev.conf.js // webpack開發環境配置 | |-- webpack.prod.conf.js // webpack生產環境配置 |-- config // 項目開發環境配置 | |-- dev.env.js // 開發環境變量 | |-- index.js // 項目一些配置變量 | |-- prod.env.js // 生產環境變量 | |-- test.env.js // 測試環境變量 |-- src // 源碼目錄 | |-- components // vue公共組件 | |-- store // vuex的狀態管理 | |-- App.vue // 頁面入口文件 | |-- main.js // 程序入口文件,加載各類公共組件 |-- static // 靜態文件,好比一些圖片,json數據等 | |-- data // 羣聊分析獲得的數據用於數據可視化 |-- .babelrc // ES6語法編譯配置 |-- .editorconfig // 定義代碼格式 |-- .gitignore // git上傳須要忽略的文件格式 |-- README.md // 項目說明 |-- favicon.ico |-- index.html // 入口頁面 |-- package.json // 項目基本信息
package.json
這裏包含開發運行,項目打包,單元測試等命令,測試的東西先放一邊,看dev和build命令。運行」npm run dev」的時候執行的是build/dev-server.js文件,運行」npm run build」的時候執行的是build/build.js文件,咱們能夠從這兩個文件開始進行代碼閱讀分析。 ``` "scripts": { "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev", "lint": "eslint --ext .js,.vue src", "build": "node build/build.js" }, ```
build/build.js
這裏包含了: loading動畫; 刪除目標文件夾; 執行webpack構建; 輸出信息; webpack編譯以後會輸出到配置裏面指定的目標文件夾;刪除目標文件夾以後再建立是爲了去除舊的內容,以避免產生不可預測的影響。 ``` 'use strict' // 檢查NodeJS和npm的版本。 require('./check-versions')() process.env.NODE_ENV = 'production' // ora插件,實現node.js命令行環境的loading效果和顯示各類狀態的圖標等。 const ora = require('ora') const rm = require('rimraf') const path = require('path') // 用於在控制檯輸出帶顏色字體的插件。 const chalk = require('chalk') const webpack = require('webpack') const config = require('../config') const webpackConfig = require('./webpack.prod.conf') const spinner = ora('building for production...') spinner.start() // rimraf插件,每次啓動編譯或者打包以前,先把整個dist文件夾刪除,而後再從新生成dist。 rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { if (err) throw err webpack(webpackConfig, (err, stats) => { spinner.stop() if (err) throw err process.stdout.write(stats.toString({ colors: true, modules: false, children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. chunks: false, chunkModules: false }) + '\n\n') if (stats.hasErrors()) { console.log(chalk.red(' Build failed with errors.\n')) process.exit(1) } console.log(chalk.cyan(' Build complete.\n')) console.log(chalk.yellow( ' Tip: built files are meant to be served over an HTTP server.\n' + ' Opening index.html over file:// won\'t work.\n' )) }) }) ```
build/check-versions.js
這裏完成對node和npm的版本檢測。 ``` 'use strict' // 用於在控制檯輸出帶顏色字體的插件。 const chalk = require('chalk') // 語義化版本檢查插件。 const semver = require('semver') // 引入package.json。 const packageConfig = require('../package.json') const shell = require('shelljs') // 開闢子進程執行指令cmd並返回結果。 function exec (cmd) { return require('child_process').execSync(cmd).toString().trim() } // node和npm版本需求。 const versionRequirements = [ { name: 'node', 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 () { const warnings = [] // 依次判斷版本是否符合要求。 for (let i = 0; i < versionRequirements.length; i++) { const 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 (let i = 0; i < warnings.length; i++) { const warning = warnings[i] console.log(' ' + warning) } console.log() process.exit(1) } } ```
build/utils.js
這裏提供工具函數,包括生成處理各類樣式語言的loader,獲取資源文件存放路徑的工具函數。 配置靜態資源路徑; 生成cssLoaders用於加載.vue文件中的樣式; 生成styleLoaders用於加載不在.vue文件中的單獨存在的樣式文件。
build/vue-loader.conf.js
這裏負責處理.vue文件中的樣式,配置css加載器以及編譯css以後自動添加前綴。 ``` 'use strict' const utils = require('./utils') const config = require('../config') const isProduction = process.env.NODE_ENV === 'production' const sourceMapEnabled = isProduction ? config.build.productionSourceMap : config.dev.cssSourceMap module.exports = { // css加載器。 loaders: utils.cssLoaders({ sourceMap: sourceMapEnabled, extract: isProduction }), cssSourceMap: sourceMapEnabled, cacheBusting: config.dev.cacheBusting, // 讓vue-loader知道須要對video的src屬性的內容轉換爲模塊。 transformToRequire: { video: ['src', 'poster'], source: 'src', img: 'src', image: 'xlink:href' } } ```
build/webpack.base.conf.js
這裏這個配置裏面只配置了.js、.vue、圖片、字體等幾類文件的處理規則,若是須要處理其餘文件能夠在module.rules裏面另行配置。從代碼中看到,dev-server使用的webpack配置來自build/webpack.dev.conf.js文件(測試環境下使用的是build/webpack.prod.conf.js,這裏暫時不考慮測試環境)。而build/webpack.dev.conf.js中又引用了webpack.base.conf.js,因此這裏先看webpack.base.conf.js。 webpack.base.conf.js主要完成下面的操做: 配置webpack編譯入口; 配置webpack輸出路徑和命名規則; 配置模塊resolve規則; 配置不一樣類型模塊的處理規則。 ``` 'use strict' const path = require('path') const utils = require('./utils') const config = require('../config') const vueLoaderConfig = require('./vue-loader.conf') // 給出正確的絕對路徑。 function resolve (dir) { return path.join(__dirname, '..', dir) } const createLintingRule = () => ({ test: /\.(js|vue)$/, loader: 'eslint-loader', enforce: 'pre', include: [resolve('src'), resolve('test')], options: { formatter: require('eslint-friendly-formatter'), emitWarning: !config.dev.showEslintErrorsInOverlay } }) module.exports = { context: path.resolve(__dirname, '../'), entry: { // 配置webpack編譯入口。 app: './src/main.js' }, // 配置webpack輸出路徑和命名規則。 output: { // webpack輸出的目標文件夾路徑(例如:/dist) path: config.build.assetsRoot, // webpack輸出bundle文件命名格式。 filename: '[name].js', // webpack編譯輸出的發佈路徑。 publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath }, // 配置模塊resolve的規則。 resolve: { // 自動resolve的擴展名。 extensions: ['.js', '.vue', '.json'], // 建立路徑別名,有了別名以後引用模塊更方便,例如:import Vue from 'vue/dist/vue.esm.js'能夠寫成 import Vue from 'vue'。 alias: { 'vue$': 'vue/dist/vue.esm.js', '@': resolve('src'), } }, // 配置不一樣類型模塊的處理規則。 module: { rules: [ ...(config.dev.useEslint ? [createLintingRule()] : []), // 對全部.vue文件使用vue-loader { test: /\.vue$/, loader: 'vue-loader', options: vueLoaderConfig }, // 對src和test文件夾下的.js文件使用babel-loader。 { test: /\.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] }, // 對圖片資源文件使用url-loader,name指明瞭輸出的命名規則。 { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }, // 對媒體資源文件使用url-loader,name指明瞭輸出的命名規則。 { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('media/[name].[hash:7].[ext]') } }, // 對字體資源文件使用url-loader,name指明瞭輸出的命名規則。 { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('fonts/[name].[hash:7].[ext]') } } ] }, node: { // prevent webpack from injecting useless setImmediate polyfill because Vue // source contains it (although only uses it if it's native). setImmediate: false, // prevent webpack from injecting mocks to Node native modules // that does not make sense for the client dgram: 'empty', fs: 'empty', net: 'empty', tls: 'empty', child_process: 'empty' } } ```
build/webpack.dev.conf.js
這裏面在webpack.base.conf的基礎上增長完善了開發環境下面的配置,主要完成下面操做: 將webpack的熱重載客戶端代碼添加到每一個entry對應的應用; 合併基礎的webpack配置; 配置樣式文件的處理規則,styleLoaders; 配置Source Maps; 配置webpack插件。 ``` 'use strict' const utils = require('./utils') const webpack = require('webpack') const config = require('../config') // 一個能夠合併數組和對象的插件。 const merge = require('webpack-merge') const path = require('path') const baseWebpackConfig = require('./webpack.base.conf') const CopyWebpackPlugin = require('copy-webpack-plugin') // 一個用於生成HTML文件並自動注入依賴文件(link/script)的webpack插件。 const HtmlWebpackPlugin = require('html-webpack-plugin') // 用於更友好地輸出webpack的警告、錯誤等信息。 const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') const portfinder = require('portfinder') const HOST = process.env.HOST const PORT = process.env.PORT && Number(process.env.PORT) // 合併基礎的webpack配置。 const devWebpackConfig = merge(baseWebpackConfig, { module: { // 配置樣式文件的處理規則,使用styleLoaders。 rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) }, // 配置Source Maps。在開發中使用cheap-module-eval-source-map更快 devtool: config.dev.devtool。 devServer: { clientLogLevel: 'warning', historyApiFallback: { rewrites: [ { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, ], }, hot: true, contentBase: false, compress: true, host: HOST || config.dev.host, // dev-server 監聽的端口,默認爲config.dev.port設置的端口,即8080。 port: PORT || config.dev.port, // 用於判斷是否要自動打開瀏覽器的布爾變量,當配置文件中沒有設置自動打開瀏覽器的時候其值爲 false。 open: config.dev.autoOpenBrowser, overlay: config.dev.errorOverlay ? { warnings: false, errors: true } : false, publicPath: config.dev.assetsPublicPath, // 定義 HTTP 代理表,代理到 API 服務器。 proxy: config.dev.proxyTable, quiet: true, // necessary for FriendlyErrorsPlugin watchOptions: { poll: config.dev.poll, } }, // 配置webpack插件。 plugins: [ new webpack.DefinePlugin({ 'process.env': require('../config/dev.env') }), new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. // 頁面中的報錯不會阻塞,可是會在編譯結束後報錯。 new webpack.NoEmitOnErrorsPlugin(), // https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html', inject: true }), // copy custom static assets new CopyWebpackPlugin([ { from: path.resolve(__dirname, '../static'), to: config.dev.assetsSubDirectory, ignore: ['.*'] } ]) ] }) module.exports = new Promise((resolve, reject) => { portfinder.basePort = process.env.PORT || config.dev.port portfinder.getPort((err, port) => { if (err) { reject(err) } else { process.env.PORT = port devWebpackConfig.devServer.port = port devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ compilationSuccessInfo: { messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], }, onErrors: config.dev.notifyOnErrors ? utils.createNotifierCallback() : undefined })) resolve(devWebpackConfig) } }) }) ```
build/webpack.prod.conf.js
這裏是構建的時候用到的webpack配置來自webpack.prod.conf.js,該配置一樣是在webpack.base.conf基礎上的進一步完善。主要完成下面操做: 合併基礎的webpack配置; 配置樣式文件的處理規則,styleLoaders; 配置webpack的輸出; 配置webpack插件; gzip模式下的webpack插件配置; webpack-bundle分析。 ``` 'use strict' const path = require('path') const utils = require('./utils') const webpack = require('webpack') const config = require('../config') const merge = require('webpack-merge') const baseWebpackConfig = require('./webpack.base.conf') const CopyWebpackPlugin = require('copy-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') // 用於從webpack生成的bundle中提取文本到特定文件中的插件,能夠抽取出css,js文件將其與webpack輸出的bundle分離。 const ExtractTextPlugin = require('extract-text-webpack-plugin') const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') const UglifyJsPlugin = require('uglifyjs-webpack-plugin') const env = require('../config/prod.env') // 合併基礎的webpack配置。 const webpackConfig = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true, usePostCSS: true }) }, devtool: config.build.productionSourceMap ? config.build.devtool : false, // 配置webpack的輸出。 output: { // 編譯輸出目錄。 path: config.build.assetsRoot, // 編譯輸出文件名格式。 filename: utils.assetsPath('js/[name].[chunkhash].js'), // 沒有指定輸出名的文件輸出的文件名格式。 chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') }, // 配置webpack插件。 plugins: [ // http://vuejs.github.io/vue-loader/en/workflow/production.html new webpack.DefinePlugin({ 'process.env': env }), // 醜化壓縮代碼 new UglifyJsPlugin({ uglifyOptions: { compress: { warnings: false } }, sourceMap: config.build.productionSourceMap, parallel: true }), // 抽離css文件。 new ExtractTextPlugin({ filename: utils.assetsPath('css/[name].[contenthash].css'), // Setting the following option to `false` will not extract CSS from codesplit chunks. // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 allChunks: true, }), // Compress extracted CSS. We are using this plugin so that possible // duplicated CSS from different components can be deduped. new OptimizeCSSPlugin({ cssProcessorOptions: config.build.productionSourceMap ? { safe: true, map: { inline: false } } : { safe: true } }), // generate dist index.html with correct asset hash for caching. // you can customize output by editing /index.html // see https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: config.build.index, template: 'index.html', inject: true, minify: { removeComments: true, collapseWhitespace: true, removeAttributeQuotes: true // more options: // https://github.com/kangax/html-minifier#options-quick-reference }, // necessary to consistently work with multiple chunks via CommonsChunkPlugin chunksSortMode: 'dependency' }), // keep module.id stable when vendor modules does not change new webpack.HashedModuleIdsPlugin(), // enable scope hoisting new webpack.optimize.ModuleConcatenationPlugin(), // split vendor js into its own file new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks (module) { // any required modules inside node_modules are extracted to vendor return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '../node_modules') ) === 0 ) } }), // extract webpack runtime and module manifest to its own file in order to // prevent vendor hash from being updated whenever app bundle is updated new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', minChunks: Infinity }), // This instance extracts shared chunks from code splitted chunks and bundles them // in a separate chunk, similar to the vendor chunk // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk new webpack.optimize.CommonsChunkPlugin({ name: 'app', async: 'vendor-async', children: true, minChunks: 3 }), // copy custom static assets new CopyWebpackPlugin([ { from: path.resolve(__dirname, '../static'), to: config.build.assetsSubDirectory, ignore: ['.*'] } ]) ] }) // gzip模式下須要引入compression插件進行壓縮。 if (config.build.productionGzip) { const CompressionWebpackPlugin = require('compression-webpack-plugin') webpackConfig.plugins.push( new CompressionWebpackPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', test: new RegExp( '\\.(' + config.build.productionGzipExtensions.join('|') + ')$' ), threshold: 10240, minRatio: 0.8 }) ) } if (config.build.bundleAnalyzerReport) { const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin webpackConfig.plugins.push(new BundleAnalyzerPlugin()) } module.exports = webpackConfig ```
build/dev-server.js
檢查node和npm的版本、引入相關插件和配置 webpack對源碼進行編譯打包並返回compiler對象 建立express服務器 配置開發中間件(webpack-dev-middleware)和熱重載中間件(webpack-hot-middleware) 掛載代理服務和中間件 配置靜態資源 啓動服務器監聽特定端口(8080) 自動打開瀏覽器並打開特定網址(localhost:8080) 說明: express服務器提供靜態文件服務,不過它還使用了http-proxy-middleware,一個http請求代理的中間件。前端開發過程當中須要使用到後臺的API的話,能夠經過配置proxyTable來將相應的後臺請求代理到專用的API服務器。
build/dev-client.js
dev-client.js裏面主要寫了瀏覽器端代碼,用於實現webpack的熱更新(實現瀏覽器端的EventSource,用於跟服務器雙向通訊webpack熱重載客戶端跟dev-server上的熱重載插件之間須要進行雙向通訊, 服務端webpack從新編譯後,會向客戶端推送信息,告訴客戶端進行更新)。
config/dev.env.js
設置了開發環境變量。
config/prod.env.js
設置了生產環境變量。
config/index.js
描述了開發和構建兩種環境下的配置,前面的build文件夾下也有很多文件引用了index.js裏面的配置。
var path = require(‘path‘) module.exports = { // 構建產品時使用的配置 build: { // 環境變量 env: require(‘./prod.env‘), // html入口文件 index: path.resolve(__dirname, ‘../dist/index.html‘), // 產品文件的存放路徑 assetsRoot: path.resolve(__dirname, ‘../dist‘), // 二級目錄,存放靜態資源文件的目錄,位於dist文件夾下 assetsSubDirectory: ‘static‘, // 發佈路徑,若是構建後的產品文件有用於發佈CDN或者放到其餘域名的服務器,能夠在這裏進行設置 // 設置以後構建的產品文件在注入到index.html中的時候就會帶上這裏的發佈路徑 assetsPublicPath: ‘/‘, // 是否使用source-map 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 // 是否開啓gzip壓縮 productionGzip: false, // gzip模式下須要壓縮的文件的擴展名,設置js、css以後就只會對js和css文件進行壓縮 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 // 是否展現webpack構建打包以後的分析報告 bundleAnalyzerReport: process.env.npm_config_report }, // 開發過程當中使用的配置 dev: { // 環境變量 env: require(‘./dev.env‘), // dev-server監聽的端口 port: 8080, // 是否自動打開瀏覽器 autoOpenBrowser: true, // 靜態資源文件夾 assetsSubDirectory: ‘static‘, // 發佈路徑 assetsPublicPath: ‘/‘, // 代理配置表,在這裏能夠配置特定的請求代理到對應的API接口 // 例如將‘localhost:8080/api/xxx‘代理到‘www.example.com/api/xxx‘ 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 cssSourceMap: false } }
本文主要介紹了vue-cli腳手架工具構建出項目結構目錄文件的相關描述信息,僅做了解使用,自身先有個印象,有助於後續運行項目發生問題較好排查些。vue