最開始學習Vue的時候,不建議直接使用模板,而應該本身從頭寫起。模板都是人寫的,要堅信「人能我能」。只有本身親自實踐,才能促進本身主動思考,才能對模板、框架有深入的理解。css
在Github上看到一個Vue的項目模板,裏面包含許多新的知識。仔細研究,所獲甚豐。html
const spinner = ora('building for production...') spinner.start() doSomeThing(args,()=>{ spinner.stop() })
const nodeNotifier=require("node-notifier") nodeNotifier.notify("hello world")
notify能夠接受一個JSON。前端
notifier.notify({ title: "title", message: "message", subtitle: 'subtitle', icon: path.join(__dirname, 'logo.png') })
NodeJs的兩大做用:vue
使用Node編寫工具經常使用的庫上面已經提到了node
{ ... "scripts":{ "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev", "unit": "jest --config test/unit/jest.conf.js --coverage", "test": "npm run unit", "lint": "eslint --ext .js,.vue src test/unit", "build": "node build/build.js" } ... }
source-map解決了SPA程序難以定位錯誤代碼行數的問題。
若是是我第一個採用SPA方式開發程序,我極可能會由於難以定位錯誤代碼行數而放棄SPA技術方案。但如今人們已經經過source-map的方式解決了這個問題。
Vue框架的工具鏈包括Chrome插件、Vue-Loader、IDE的Vue插件、Vue-Cli等,這些工具鏈造就了Vue的易用性。
任何好庫必然都有一個優秀的工具鏈,工具鏈可以促進庫的推廣。
適當地學習經常使用系統的插件開發是製做工具的必備常識,要掌握VSCode、Chrome、Intellij Idea等系統的插件開發。webpack
check-version.js文件用於檢測系統環境,即檢測node和npm的版本是否知足要求。
一個好的應用應該主動檢測環境,不然一旦出錯,就會無從下手。git
check-version.jsgithub
const chalk = require("chalk") const semver = require("semver") const packageConfig = require("../package.json") const shell = require("shelljs") function exec(cmd) { return require("child_process") .execSync(cmd) .toString() .trim() } 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(chalk.yellow("To use this template, you must update following to modules:")) for (let i = 0; i < warnings.length; i++) { const warning = warnings[i] console.log(" " + warning) } console.log() process.exit(1) } }
mode有三種模式:web
測試環境和開發環境的配置幾乎是徹底同樣的,生產環境在打包方面進行了許多優化,使得打包文件更小。shell
當執行npm run build時,mode爲production。此命令執行如下步驟:
build.js
require('./check-versions')() process.env.NODE_ENV = 'production' 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() 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' )) }) })
在這段代碼中,須要掌握在JS中調用webpack,webpack第一個參數爲配置對象,第二個參數爲回調函數,回調函數包括兩個參數(err,stats)。
在NodeJS中引入其它模塊時,使用const xxx=require("moduleName")
的寫法。注意若是引入的模塊是js文件,不要帶文件後綴名.js。
console.log(global.exports)//undifined console.log(exports)//{} console.log(module.exports)//{} exports = "haha" console.log(exports)//"haha" console.log(module.exports)//{}
const haha=require("./haha.json") //等價於 const haha=JSON.parse(fs.readFileSync(path.join(__dirname,'haha.json')).toString("utf8"))
寫CSS有不少選擇:css、less、sass、scss、stylus、postcss。若是爲這些語言都配置loader,webpack的配置會顯得很是冗長。webpack配置本質就是JSON,咱們可使用JavaScript來生成JSON。
const cssLoaders = function (options) { options = options || {} const cssLoader = { loader: 'css-loader', options: { sourceMap: options.sourceMap } } const postcssLoader = { loader: 'postcss-loader', options: { sourceMap: options.sourceMap } } // generate loader string to be used with extract text plugin function generateLoaders (loader, loaderOptions) { const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] if (loader) { loaders.push({ loader: loader + '-loader', options: Object.assign({}, loaderOptions, { sourceMap: options.sourceMap }) }) } // Extract CSS when that option is specified // (which is the case during production build) if (options.extract) { return ExtractTextPlugin.extract({ use: loaders, fallback: 'vue-style-loader' }) } else { return ['vue-style-loader'].concat(loaders) } } // https://vue-loader.vuejs.org/en/configurations/extract-css.html return { css: generateLoaders(), postcss: generateLoaders(), less: generateLoaders('less'), sass: generateLoaders('sass', { indentedSyntax: true }), scss: generateLoaders('sass'), stylus: generateLoaders('stylus') } } // Generate loaders for standalone style files (outside of .vue) exports.styleLoaders = function (options) { const output = [] const loaders = cssLoaders(options) for (const extension in loaders) { const loader = loaders[extension] output.push({ test: new RegExp('\\.' + extension + '$'), use: loader }) } return output }
在配置VueLoader時,不須要指明less的loader。VueLoader會根據webpack配置的規則尋找loader,就好像Vue中的CSS部分就像單個文件同樣。
打包時,不必把一些包打包進去。
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" }
設置webpack尋找文件的根目錄,這樣就不用每一個路徑都是用path.join(__dirname,xxxx)
這種複雜寫法了。context選項的值必須是絕對路徑,entry 和 module.rules.loader選項相對於此路徑進行解析。不建議使用context,由於它語義不夠明確,不如手動路徑拼接來得清晰。
context: path.resolve(__dirname, "../"),
vue$
表示精確匹配vue。require('xxxx')
中是否強制要求帶後綴名,默認爲falseresolve: { extensions: [".js", ".vue", ".json"], alias: { vue$: "vue/dist/vue.esm.js", "@": path.join(__dirname, "../src") } }
rules配置主要包括vue配置vue-loader,js配置babel-loader,各類類型的資源文件配置url-loader。這是一份基本配置,更多配置須要在開發模式和生產模式中補充。
module: { rules: [ { test: /\.vue$/, loader: "vue-loader", options: vueLoaderConfig }, { test: /\.js$/, loader: "babel-loader", include: [path.join(__dirname,"src"), path.join(__dirname,"test"), path.join(__dirname,"node_modules/webpack-dev-server/client")] }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: "url-loader", options: { limit: 10000, 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]") } } ] }
開發模式的webpack配置文件名爲webpack.dev.conf.js。webpack編譯時不必定接受一個JSON對象,也能夠接受一個Promise對象。在開發模式下module.exports就是一個Promise對象。使用Promise的緣由是,開發服務器的端口號須要使用portfinder動態肯定。
下面代碼中的devWebpackConfig就是開發模式下的基本配置。
webpack.dev.conf.js
const portfinder = require('portfinder') module.exports = new Promise((resolve, reject) => { portfinder.basePort = process.env.PORT || config.dev.port portfinder.getPort((err, port) => { if (err) { reject(err) } else { // publish the new Port, necessary for e2e tests process.env.PORT = port // add port to devServer config devWebpackConfig.devServer.port = port // Add FriendlyErrorsPlugin 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) } }) })
webpack提供了豐富的插件,每一個插件都有不一樣的配置,瞭解經常使用插件是頗有必要的,可是許多插件又是無關緊要的。
plugins: [ new webpack.DefinePlugin({ //config/dev.env中定義了一些常量 'process.env': require('../config/dev.env') }), //模塊熱替換插件 new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin(), // NMP 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: ['.*'] } ]) ]
DifinePlugin咱們徹底能夠手動實現,過程也不復雜,我認爲本身實現可讀性、靈活性更好,不必定義成一個插件。
HotModuleReplacementPlugin、NamedModulesPlugin、NoEmitOnErrorsPlugin這些插件用來在瀏覽器端或者在控制檯下打印更規整信息,便於調試。
HtmlWebpackPlugin這個插件更是沒有必要,它提供的功能太簡單了。
CopyWebpackPlugin這個插件很是有用,當須要把靜態文件原封不動地複製到dist目錄時,使用這個插件。
若是index.html在dist目錄中,那麼咱們就須要使用CopyWebpackPlugin把靜態文件都放到dist目錄中。開發時devServer須要配置contentBase='dist',從而指明index.html的位置。
開發模式下的配置在基本配置的基礎上略加修改。
const utils = require("./utils") const config = require("../config") const merge = require("webpack-merge") const path = require("path") const baseWebpackConfig = require("./webpack.base.conf") const HOST = process.env.HOST const PORT = process.env.PORT && Number(process.env.PORT) const devWebpackConfig = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) }, // these devServer options should be customized in /config/index.js devServer: { clientLogLevel: "warning", historyApiFallback: { rewrites: [{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, "index.html") }] }, hot: true, contentBase: false, // since we use CopyWebpackPlugin. compress: true, host: HOST || config.dev.host, port: PORT || config.dev.port, open: config.dev.autoOpenBrowser, overlay: config.dev.errorOverlay ? { warnings: false, errors: true } : false, publicPath: config.dev.assetsPublicPath, proxy: { '/api': { target: 'http://localhost:8081', // secure: false, // 若是是https接口,須要配置這個參數 changeOrigin: true, // 若是接口跨域,須要進行這個參數配置 pathRewrite: { '^/api': '' } } } } })
module: { rules: utils.styleLoaders({ sourceMap: false, extract: true, usePostCSS: true }) }
在plugin中
// extract css into its own file 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, })
優化CSS,去掉不一樣組件之間的重複CSS
new OptimizeCSSPlugin({ cssProcessorOptions: config.build.productionSourceMap ? { safe: true, map: { inline: false } } : { safe: true } })
輸出的文件名須要帶上chunkhash
output: { path: config.build.assetsRoot, filename: utils.assetsPath('js/[name].[chunkhash].js'), chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') }
須要使用CommonsChunkWebpackPlugin
// 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 })
HtmlWebpackPlugin並不是一無可取,對於多個entry的webpack項目,它可以爲每一個入口生成index.html
new HtmlWebpackPlugin({ filename: process.env.NODE_ENV === 'testing' ? 'index.html' : 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' })
const CompressionWebpackPlugin = require('compression-webpack-plugin') webpackConfig.plugins.push( new CompressionWebpackPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', test: new RegExp( '\\.(' + ['js', 'css'].join('|') + ')$' ), threshold: 10240, minRatio: 0.8 }) )
優化前端項目有以下幾個出發點:
具體措施: