您好,登錄后才能下訂單哦!
上次給大家分享的是用vue-cli快速搭建vue項目,雖然很省時間和精力,但想要真正搞明白,我們還需要對其原理一探究竟。
大家拿到一個項目,要快速上手,正確的思路是這樣的:
首先,如果在項目有readme.md的情況下,大家要先讀readme,項目的一些基本介紹,包括項目信息、運行的腳本、采用何種框架,以及項目維護者等信息通常都會有。一般在git上維護的項目都會有readme.md,不熟悉markdown語法的同學可以先了解下markdown入門。
第二步,要看package.json?,F(xiàn)代的前端項目中通常都會有package.json文件。在package.json里,會介紹項目名稱、版本、描述、作者、腳本、依賴包,對環(huán)境的要求,以及對瀏覽器要求。
{ "name": "uccn", "version": "1.0.0", "description": "uccn3.0", "author": "v_yangtianjiao <v_yangtianjiao@baidu.com>", "private": true, // 這里的腳本是分析項目的主要入口 "scripts": { "dev": "node build/dev-server.js", "start": "node build/dev-server.js", "build": "node build/build.js", "jsonp": "node build/jsonp-server.js" }, // 項目依賴 "dependencies": { "fetch-jsonp": "^1.1.3", "less": "^2.7.2", "less-loader": "^4.0.4", "stylus": "^0.54.5", "stylus-loader": "^3.0.1", "vue": "^2.4.2" }, "devDependencies": { "autoprefixer": "^7.1.2", "babel-core": "^6.22.1", "babel-loader": "^7.1.1", "babel-plugin-component": "^0.10.1", "babel-plugin-transform-runtime": "^6.22.0", "babel-preset-env": "^1.3.2", "babel-preset-es2015": "^6.24.1", "babel-preset-stage-2": "^6.22.0", "babel-register": "^6.22.0", "chalk": "^2.0.1", "connect-history-api-fallback": "^1.3.0", "copy-webpack-plugin": "^4.0.1", "css-loader": "^0.28.0", "cssnano": "^3.10.0", "eventsource-polyfill": "^0.9.6", "express": "^4.14.1", "extract-text-webpack-plugin": "^2.0.0", "file-loader": "^0.11.1", "friendly-errors-webpack-plugin": "^1.1.3", "html-webpack-plugin": "^2.28.0", "http-proxy-middleware": "^0.17.3", "opn": "^5.1.0", "optimize-css-assets-webpack-plugin": "^2.0.0", "ora": "^1.2.0", "rimraf": "^2.6.0", "semver": "^5.3.0", "shelljs": "^0.7.6", "url-loader": "^0.5.8", "vue-loader": "^13.0.4", "vue-style-loader": "^3.0.1", "vue-template-compiler": "^2.4.2", "webpack": "^2.6.1", "webpack-bundle-analyzer": "^2.2.1", "webpack-dev-middleware": "^1.10.0", "webpack-hot-middleware": "^2.18.0", "webpack-merge": "^4.1.0" }, // 對node版本的以及npm版本的要求 "engines": { "node": ">= 4.0.0", "npm": ">= 3.0.0" }, // 瀏覽器要求,vue項目不支持ie8,因為ie8是es3,尚沒有Object.defineProperty屬性 "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ] }
上面的package.json是從實際vue項目中摘出來的,大家從package.json中就會對項目有一個大概的了解,最主要的是腳本部分。通過npm的自動化任務(wù),可以很方便的執(zhí)行配置文件中的腳本。通過配置 "jsonp": "node build/jsonp-server.js",可以方便的使用npm run jsonp命令,代替node build/jsonp-server.js或者更復(fù)雜的一系列命令。詳細的npm自動化命令可以移步npm 自動化。
現(xiàn)在的項目目錄結(jié)構(gòu)如上,我們從剛才的腳本入手。首先是啟服務(wù)的腳本npm run dev,實際上是執(zhí)行node build/dev-server.js,我們在build文件夾中找到dev-server.js,一步步分析。
在上面的dev-server中,有很多變量來自于./config/index.js和webpack.dev.conf.js,我們一個個看上述配置文件。
首先看./config/index.js,這里是整個項目主要的配置入口,我們在代碼中一步步分析:
// node自帶路徑工具. var path = require('path') // 分為兩種環(huán)境,dev和production module.exports = { build: { env: require('./prod.env'),// 使用config/prod.env.js中定義的編譯環(huán)境 index: path.resolve(__dirname, '../dist/index.html'),// 編譯輸入的index.html文件。node.js中,在任何模塊文件內(nèi)部,可以使用__filename變量獲取當前模塊文件的帶有完整絕對路徑的文件名, assetsRoot: path.resolve(__dirname, '../dist'),// 編譯輸出的靜態(tài)資源路徑 assetsSubDirectory: 'static',// 編譯輸出的二級目錄 assetsPublicPath: './', // 編譯發(fā)布的根目錄,可配置為資源服務(wù)器或者cdn域名 productionSourceMap: false,//是否開啟cssSourceMap productionGzip: false,// 是否開啟gzip productionGzipExtensions: ['js', 'css'],// 需要用gzip壓縮的文件擴展名 bundleAnalyzerReport: process.env.npm_config_report }, dev: { env: require('./dev.env'), port: 8989,// 起服務(wù)的端口 autoOpenBrowser: true, assetsSubDirectory: 'static', assetsPublicPath: '/', proxyTable: {},// 需要代理的接口,可以跨域 cssSourceMap: false } }
接著我們分析webpack.dev.conf.js:
var utils = require('./utils')// 工具類 var webpack = require('webpack') var config = require('../config') var merge = require('webpack-merge')// 使用webpack配置合并插件 var baseWebpackConfig = require('./webpack.base.conf') var HtmlWebpackPlugin = require('html-webpack-plugin')// 這個插件自動生成HTML,并注入到.html文件中 var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') // 將hot-reload相對路徑添加到webpack.base.conf的對應(yīng)的entry前面 Object.keys(baseWebpackConfig.entry).forEach(function (name) { baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) }) // webpack.dev.conf.js與webpack.base.conf.js中的配置合并 module.exports = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) }, // webpack-devtool有7種模式,cheap-module-eval-source-map模式是比較快的開發(fā)模式 devtool: '#cheap-module-eval-source-map', plugins: [ // 你可以理解為,通過配置了DefinePlugin,那么這里面的標識就相當于全局變量,你的業(yè)務(wù)代碼可以直接使用配置的標識。 new webpack.DefinePlugin({ 'process.env': config.dev.env }), // hotModule插件讓頁面變動時,只重繪對應(yīng)的模塊,不會重繪整個HTML文件 new webpack.HotModuleReplacementPlugin(), // 在編譯出現(xiàn)錯誤時,使用 NoEmitOnErrorsPlugin 來跳過輸出階段。這樣可以確保輸出資源不會包含錯誤 new webpack.NoEmitOnErrorsPlugin(), // 將生成的HTML代碼注入index.html文件 new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html', inject: true }), // friendly-errors-webpack-plugin用于更友好地輸出webpack的警告、錯誤等信息 new FriendlyErrorsPlugin() ] })
剛才的webpack.dev.conf.js中有引到webpack.base.conf.js,我們就把他們一網(wǎng)打盡,繼續(xù)看webpack.base.conf.js!
/* eslint-disable */ var path = require('path')// node自帶的文件路徑插件 var utils = require('./utils')// 工具類 var config = require('../config')// 上面說過的config/index var vueLoaderConfig = require('./vue-loader.conf')// vue-loader.conf配置文件是用來解決各種css文件的,定義了諸如css,less,sass之類的和樣式有關(guān)的loader // 此函數(shù)是用來返回當前目錄的平行目錄的路徑, function resolve (dir) { return path.join(__dirname, '..', dir) } module.exports = { entry: { uccn: './src/main.js'// 入口 }, output: { // 路徑是config目錄下的index.js中的build配置中的assetsRoot,也就是dist目錄 path: config.build.assetsRoot, filename: '[name].js', // 上線地址,也就是真正的文件引用路徑,如果是production生產(chǎn)環(huán)境,其實這里都是 '/' publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath }, // resolve是webpack的內(nèi)置選項,顧名思義,決定要做的事情,也就是說當使用 import "jquery",該如何去執(zhí)行這件事情,就是resolve配置項要做的,import jQuery from "./additional/dist/js/jquery" 這樣會很麻煩,可以起個別名簡化操作 resolve: { // 省略擴展名,比方說import index form '../js/index', 會默認去找index文件,然后找index.js,.vue,.josn. extensions: ['.js', '.vue', '.json'], alias: { 'vue$': 'vue/dist/vue.esm.js', // 使用上面的resolve函數(shù),意思是用@代替src的絕對路徑 '@': resolve('src'), } }, // 不同的模塊使用不同的loader module: { rules: [ { // 對vue文件,使用vue-loader解析 test: /\.vue$/, loader: 'vue-loader', options: vueLoaderConfig }, { // babel-loader把es6解析成es5 test: /\.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test')] }, { // url-loader將文件大小低于下面option中l(wèi)imit的圖片,轉(zhuǎn)化為一個64位的DataURL,這樣會省去很多請求,大于limit的,按[name].[hash:7].[ext]的命名方式放到了static/img下面,方便做cache test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 20000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }, { // 音頻和視頻文件處理,同上 test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('media/[name].[hash:7].[ext]') } }, { // 字體處理,同上 test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('fonts/[name].[hash:7].[ext]') } } ] } }
至此,npm run dev起本地開發(fā)環(huán)境相關(guān)的配置文件基本說完了,接著說一下上面都用到的util工具類:
var path = require('path') var config = require('../config') // extract-text-webpack-plugin該插件的主要是為了抽離css樣式,防止將樣式打包在js中引起頁面樣式加載錯亂的現(xiàn)象 var ExtractTextPlugin = require('extract-text-webpack-plugin') // 返回資源文件路徑,path.posix以posix兼容的方式交互,是跨平臺的,如果是path.win32的話,只能在win上 exports.assetsPath = function (_path) { var assetsSubDirectory = process.env.NODE_ENV === 'production' ? config.build.assetsSubDirectory : config.dev.assetsSubDirectory return path.posix.join(assetsSubDirectory, _path) } // 通過判斷是否是生產(chǎn)環(huán)境,配置不同的樣式語言的loader配置 exports.cssLoaders = function (options) { options = options || {} var cssLoader = { loader: 'css-loader', options: { minimize: process.env.NODE_ENV === 'production', sourceMap: options.sourceMap } } // 生成各種loader配置,通過傳入不同的loader和option,將不同樣式文件語言的loader拼好,push到loader配置中。 function generateLoaders (loader, loaderOptions) { var loaders = [cssLoader] if (loader) { loaders.push({ loader: loader + '-loader', options: Object.assign({}, loaderOptions, { sourceMap: options.sourceMap }) }) } // extract-text-webpack-plugin有三個參數(shù),use指需要用什么loader去編譯文件;fallback指編譯后用什么loader去提取文件;還有一個publicfile用來覆蓋項目路徑 if (options.extract) { return ExtractTextPlugin.extract({ use: loaders, fallback: 'vue-style-loader' }) } else { return ['vue-style-loader'].concat(loaders) } } // 對不同的樣式語言,返回相應(yīng)的loader return { css: generateLoaders(), postcss: generateLoaders(), less: generateLoaders('less'), sass: generateLoaders('sass', { indentedSyntax: true }), scss: generateLoaders('sass'), stylus: generateLoaders('stylus'), styl: generateLoaders('stylus') } } // 生成處理不同的樣式文件處理規(guī)則 exports.styleLoaders = function (options) { var output = [] var loaders = exports.cssLoaders(options) for (var extension in loaders) { var loader = loaders[extension] output.push({ test: new RegExp('\\.' + extension + '$'), use: loader }) } return output }
———————————————— 華麗的分隔符 —————————————————
下面我們繼續(xù)說npm run build,打包編譯的一系列操作~
從package.json 中可以看出,npm run build,其實是執(zhí)行了 node build/build.js,我們在build文件夾中找到build.js,build主要的工作是:檢測node和npm版本,刪除dist包,webpack構(gòu)建打包,在終端輸出構(gòu)建信息并結(jié)束,如果報錯,則輸出報錯信息。
require('./check-versions')() process.env.NODE_ENV = 'production' // 在終端顯示的旋轉(zhuǎn)器插件 var ora = require('ora') // 用于刪除文件夾 var rm = require('rimraf') var path = require('path') // 終端文字顏色插件 var chalk = require('chalk') var webpack = require('webpack') var config = require('../config') var webpackConfig = require('./webpack.prod.conf') var spinner = ora('building for production...') spinner.start() // 刪除dist文件夾,之后webpack打包 rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { if (err) throw err webpack(webpackConfig, function (err, stats) { spinner.stop() if (err) throw err process.stdout.write(stats.toString({ colors: true, modules: false, children: false, 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.js用到了webpack.prod.conf.js,他與webpack.base.conf.js merge之后,作為webpack配置文件,我們再看看webpack.prod.conf.js,主要做的工作是:
1.提取webpack生成的bundle中的文本,到特定的文件,使得css,js文件與webpack輸出的bundle分離。
2.合并基本的webpack配置
3.配置webpack的輸出,包括輸出路徑,文件名格式。
4.配置webpack插件,包括丑化代碼。
5.gzip下引入compression插件進行壓縮。
/* eslint-disable */ var path = require('path') var utils = require('./utils') var webpack = require('webpack') var config = require('../config') var merge = require('webpack-merge') var baseWebpackConfig = require('./webpack.base.conf') var CopyWebpackPlugin = require('copy-webpack-plugin') var HtmlWebpackPlugin = require('html-webpack-plugin') // 用于從webpack生成的bundle中提取文本到特定文件中的插件 // 可以抽取出css,js文件將其與webpack輸出的bundle分離 var ExtractTextPlugin = require('extract-text-webpack-plugin') var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') var env = config.build.env // 合并基礎(chǔ)的webpack配置 var webpackConfig = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) }, // 7中sourceMap上面有講過 devtool: config.build.productionSourceMap ? '#source-map' : false, // 配置webpack輸出的目錄,及文件命名規(guī)則 output: { path: config.build.assetsRoot, filename: utils.assetsPath('js/[name].min.js'), chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') }, // webpack插件配置 plugins: [ // 同webpack.dev.conf.js new webpack.DefinePlugin({ 'process.env': env }), // 丑化代碼 new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false }, sourceMap: true }), // 抽離css文件到單獨的文件 new ExtractTextPlugin({ filename: utils.assetsPath('css/[name].min.css') }), new OptimizeCSSPlugin({ cssProcessorOptions: { safe: true } }), // 生成并注入index.html new HtmlWebpackPlugin({ filename: config.build.index, template: 'index.html', inject: true, minify: { removeComments: true, collapseWhitespace: false, removeAttributeQuotes: true }, chunksSortMode: 'dependency' }), // keep module.id stable when vender modules does not change new webpack.HashedModuleIdsPlugin(), split vendor js into its own file new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: function (module, count) { // 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', chunks: ['vendor'] }), copy custom static assets new CopyWebpackPlugin([ { from: path.resolve(__dirname, '../static'), to: config.build.assetsSubDirectory, ignore: ['.*'] } ]) ] }) // gzip模式下需要引入compression插件進行壓縮 if (config.build.productionGzip) { var 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) { var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin webpackConfig.plugins.push(new BundleAnalyzerPlugin()) } module.exports = webpackConfig
到此為止,vue官方腳手架工具vue-cli 2.0的所有配置文件都已介紹完畢,從頭到尾再梳理一遍:
執(zhí)行npm run dev或者npm run start,實際是在node環(huán)境執(zhí)行build/dev-server.js, dev-server.js會去拿到config中的端口等配置,通過express起一個服務(wù),通過插件自動打開瀏覽器,加載webpack編譯后放在內(nèi)存的bundle。
執(zhí)行npm run build,實際上執(zhí)行了build/build.js,通過webpack的一系列配置及插件,將文件打包合并丑化,并創(chuàng)建dist目錄,放置編譯打包后的文件,這將是未來用在生產(chǎn)環(huán)境的包。
寫這篇文章我自身的收獲也挺多,第一是對vue-cli整體的認知更加清晰條理,第二是對webpack的一些插件有了新的認識。以前對一些插件模棱兩可,直接越過,這是不對的,要一步一個腳印兒,遇坑填坑,這樣才會有收獲。雖然過程可能是艱辛的,但收獲將會是巨大的~希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。