vue-cli#4.7專案結構分析

weixin_34127717發表於2018-05-11

前言

使用過 vue 進行專案開發的同學,一定知道或者使用過 vue-cli 腳手架,他能夠很好的搭建專案結構和工程,讓我們能夠把足夠的精力放在業務開發上。也正是因為這樣,很多時候我們會因為專案工期短等原因來不及或則不會刻意去了解專案工程配置,我們今天不去介紹腳手架的使用,我們去了解下腳手架為我們建立好的打包工程是怎麼做的。

專案結構

    ├── build --------------------------------- webpack相關配置檔案
    │   ├── build.js --------------------------webpack打包配置檔案
    │   ├── check-versions.js ------------------------------ 檢查npm,nodejs版本
    │   ├── logo.png ---------------------------------- 專案 logo
    │   ├── utils.js --------------------------------------- 配置資源路徑,配置css載入器
    │   ├── vue-loader.conf.js ----------------------------- 配置css載入器等
    │   ├── webpack.base.conf.js --------------------------- webpack基本配置
    │   ├── webpack.dev.conf.js ---------------------------- 用於開發的webpack設定
    │   ├── webpack.prod.conf.js --------------------------- 用於打包的webpack設定
    ├── config ---------------------------------- 配置檔案
           ├── index.js ------------------------------ 開發和生產環境配置檔案
    ├── node_modules ---------------------------- 存放依賴的目錄
    ├── src ------------------------------------- 原始碼
    │   ├── assets ------------------------------ 靜態檔案
    │   ├── components -------------------------- 元件
    │   ├── main.js ----------------------------- 主js
    │   ├── App.vue ----------------------------- 專案入口元件
    │   ├── router ------------------------------ 路由
    ├── package.json ---------------------------- node配置檔案
    ├── .babelrc--------------------------------- babel配置檔案
    ├── .editorconfig---------------------------- 編輯器配置
    ├── .gitignore------------------------------- 配置git可忽略的檔案

webpack配置劃重點

在看專案配置檔案之前,我們先了解下 webpack 幾個常用的工具和外掛,如果你已經十分熟悉,你可以跳過這一小節,直接去看,配置檔案解析

1. path模組

path 是 node.js 中的一個模組,用於處理目錄的物件,提高開發效

常用方法:
path.join(): 用於連線路徑。該方法的主要用途在於,會正確使用當前系統的路徑分隔符,Unix 系統是 ”/“,Windows系統是 ”\“
path.resolve() 用於將相對路徑轉為絕對路徑

常使用的檔案路徑
__dirname: 總是返回被執行的 js 所在資料夾的絕對路徑
__filename: 總是返回被執行的 js 的絕對路徑
process.cwd(): 總是返回執行 node 命令時所在的資料夾的絕對路徑


2.process

process物件是Node的一個全域性物件,提供當前Node程式的資訊。

process 物件提供一系列屬性,用於返回系統資訊
process.argv:返回當前程式的命令列引數陣列。
process.env:返回一個物件,成員為當前Shell的環境變數,比如process.env.HOME
process.pid:當前程式的程式號

3.Source map

簡單說,Source map就是一個資訊檔案,裡面儲存著位置資訊。也就是說,轉換後的程式碼的每一個位置,所對應的轉換前的位置。有了它,出錯的時候,debug 工具將直接顯示原始程式碼,而不是轉換後的程式碼。這無疑給開發者帶來了很大方便。webpack 的 devtool裡有 7種 SourceMap 模式

模式 解釋
eval 每個 module 會封裝到 eval 裡包裹起來執行,並且會在末尾追加註釋 //@ sourceURL
source-map 生成一個 SourceMap 檔案.
hidden-source-map 和 source-map 一樣,但不會在 bundle 末尾追加註釋.
inline-source-map 生成一個 DataUrl 形式的 SourceMap 檔案.
eval-source-map 每個 module 會通過 eval() 來執行,並且生成一個 DataUrl 形式的 SourceMap .
cheap-source-map 生成一個沒有列資訊(column-mappings)的 SourceMaps 檔案,不包含 loader 的 sourcemap(譬如 babel 的 sourcemap)
cheap-module-source-map 生成一個沒有列資訊(column-mappings)的 SourceMaps 檔案,同時 loader 的 sourcemap 也被簡化為只包含對應行的。

4. webpack-merge

開發環境(development)和生產環境(production)的構建目標差異很大。在開發環境中,我們需要具有強大的、具有實時重新載入(live reloading)或熱模組替換(hot module replacement)能力的 source map 和 localhost server。而在生產環境中,我們的目標則轉向於關注更小的 bundle,更輕量的 source map,以及更優化的資源,以改善載入時間。由於要遵循邏輯分離,我們通常建議為每個環境編寫彼此獨立的 webpack 配置。通用的配置部分,我們抽象出一個公共檔案,通過 webpack-merge 工具的“通用”配置,我們不必在環境特定的配置中重複程式碼。

5. ExtractTextWebpackPlugin

ExtractTextWebpackPlugin 外掛通常用來做樣式檔案的分離,被分離的檔案不會被內嵌到 JS bundle 中,而會被放到一個單獨的檔案中,在樣式檔案比較大的時候,能夠提前樣式的載入,配置示例如下

const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
   module: {
      rules: [
      {
         test: /\.css$/,
         use: ExtractTextPlugin.extract({
         fallback: "style-loader",
         use: "css-loader"
      })
   }]
},
    plugins: [
        new ExtractTextPlugin("styles.css"),
    ]
}

它會將所有的入口 chunk(entry chunks)中引用的 *.css,移動到獨立分離的 CSS 檔案。因此,你的樣式將不再內嵌到 JS bundle 中,而是會放到一個單獨的 CSS 檔案(即 styles.css)當中。 如果你的樣式檔案大小較大,這會做更快提前載入,因為 CSS bundle 會跟 JS bundle 並行載入。

6.html-webpack-plugin

如果你有多個 webpack 入口點, 他們都會在生成的HTML檔案中的 script 標籤內。如果你有任何 CSS assets 在 webpack 的輸出中(例如, 利用ExtractTextPlugin提取CSS), 那麼這些將被包含在HTML head中的<link>標籤內。通常在開發中,我們為了避免 CDN 和瀏覽器的快取通常會個輸出檔案 bundle.js 加上一個hash 值例如 [hash].bundle.js,使用 html-webpack-plugin 能夠在建立新的 html 檔案的時候將我們把帶有雜湊值的 bundle.js 引用到 html 檔案.

7.optimize-css-assets-webpack-plugin

用來優化從指令碼里提煉出來的 css ,配置示例如下

var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('styles.css'),
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.optimize\.css$/g,
      cssProcessor: require('cssnano'),
      cssProcessorOptions: { discardComments: { removeAll: true } },
      canPrint: true
    })
  ]
};

8.CopyWebpackPlugin

CopyWebpackPlugin從外掛名稱上我們不難看出他的作用,通常用來拷貝資源,對專案檔案進行歸類整合

9.friendly-errors-webpack-plugin

friendly-errors-webpack-plugin能夠更好在終端看到webapck執行的警告和錯誤,提高開發體驗

10.UglifyjsWebpackPlugin

UglifyjsWebpackPlugin用來壓縮 js 程式碼

11.開發中 Server(DevServer)

webpack 專案服務,我們通常會在開發階段用來配置專案的熱重新整理,服務壓縮,專案代理等,常用的幾個配置引數介紹如下

const config = require('../config')

// config 檔案裡做了使用者自定的服務引數配置

devServer: {
    clientLogLevel: 'warning',  // 在開發攻擊的控制檯中顯示資訊,便於開發除錯,你可以將引數配置成 "none" 來進行關閉
     historyApiFallback: { // 當使用 HTML5 History API 時,任意的 404 響應都可能需要被替代為 index.html
        rewrites: [
           { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
        ],
     },
     hot: true,   //啟用專案的熱重新整理,即模組熱替換特性
     contentBase: false,   // 告訴伺服器從哪裡提供內容。只有在你想要提供靜態檔案時才需要。這裡禁用,因為配置了 CopyWebpackPlugin 的使用
     compress: true,
     host: HOST || config.dev.host,   //指定使用一個域名。預設是 localhost
     port: PORT || config.dev.port,   //指定要監聽請求的埠號:
     open: config.dev.autoOpenBrowser, //open 引數配置,如果配置成 true ,專案啟動後會自動開啟瀏覽器
     overlay: config.dev.errorOverlay   //當有錯誤或則警告的時候在頁面上顯示一個全屏的遮罩提示
         ? { warnings: false, errors: true }
         : false,
     publicPath: config.dev.assetsPublicPath, //此路徑下的打包檔案可在瀏覽器中訪問
     proxy: config.dev.proxyTable,           //代理API的請求
     quiet: true,       //啟用 quiet 後,除了初始啟動資訊之外的任何內容都不會被列印到控制檯,特別是使用了 FriendlyErrorsPlugin 外掛的時候
     watchOptions: {   //與監視檔案相關的控制選項。是否使用輪詢
           poll: config.dev.poll,
     }
},


配置檔案解析

通過了解了上面的配置,我們應該對 webpack 的常用外掛和工具有了一定了解,我們來看下 vue-cli 腳手架給我們生成的配置情況

config.js

'use strict'

const path = require('path') // 引用專案的 path 模組

module.exports = {
dev: {

// 路徑配置
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},

// 各種開發服務配置
host: 'localhost', // 開發環境域名 可以被 node 全域性變數process.env.HOST 重寫
port: 8080, //配置開發服務埠,可以被 node 全域性變數 process.env.PORT 重寫, 需要使用未被佔用的埠
autoOpenBrowser: false, //服務啟動是否自動代開瀏覽器
errorOverlay: true,   //是否在發生錯誤的時候,在頁面整屏增加一個錯誤遮罩
notifyOnErrors: true,  //是否通知錯誤 ,在我們的專案配置中和 friendly-errors-webpack-plugin 結合使用
poll: false, // 服務監聽是否輪詢操作

// 配飾是否使用 Eslint Loader 進行語法檢測
// 如果使用,在開發構建階段,會對你的程式碼會進行檢測
// 檢測出來的警告和錯誤會白展示在開發工具的控制檯

useEslint: true,  //進行語法檢測

// 配置是否將 eslint 語法檢測的警告和錯誤展示在頁面整屏的遮罩上

showEslintErrorsInOverlay: false,  // 語法檢測的警告和錯誤不展示在遮罩上

/**
 * Source Maps
 */

// https://webpack.js.org/configuration/devtool/#development
// 在上面的介紹中,我們知道 source map 是用來將我們構建後被轉化的程式碼對應構建前的程式碼,便於 debug
// cheap-module-eval-source-map 和我們介紹的 cheap-module-source-map 很類似,但是 SourceMap 會被作為資料新增到包中
devtool: 'cheap-module-eval-source-map',

// 如果你的開發工具不能進行 vue-files 的 debug ,可以將以下設定設定成 false

cacheBusting: true,

cssSourceMap: true

},

build: {

// index.html 檔案模板
index: path.resolve(__dirname, '../dist/index.html'),

// 打包路徑配置
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',

/**
 * Source Maps
 */

//生產環境 source map 配置

productionSourceMap: true,
devtool: '#source-map',

// 因為很多的主流服務都會 通過 gzip 壓縮過你的所有靜態資源,我們的配置預設不開啟 gzip
// 如果要設定成開啟,請先確保已經安裝好 compression-webpack-plugin 外掛
productionGzip: false,
productionGzipExtensions: ['js', 'css'],

// 啟動 build 命令的時候,額外新增一個引數,打包後會自動生成一個分析報告檔案,例如 npm run build --report ,可以通過配置 true ,false 來關閉
bundleAnalyzerReport: process.env.npm_config_report

}
}

check-versions.js

這個檔案主要是用來檢測當前環境中的node和npm版本和我們需要的是否一致的。

'use strict'
const chalk = require('chalk')  // 改變命令列中的字型顏色,大致這樣用chalk.blue('Hello world')
const semver = require('semver')  //是用來對特定的版本號做判斷的

const packageConfig = require('../package.json')  // 專案 npm 配置檔案,獲取依賴及版本資訊,requrie返回的就是json物件
const shell = require('shelljs') //用來執行Unix系統命令,呼叫系統命令更加方便

//把cmd這個引數傳遞的值轉化成前後沒有空格的字串,也就是版本號
function exec (cmd) {
  return require('child_process').execSync(cmd).toString().trim()
}


const versionRequirements = [
  {
    name: 'node',
    currentVersion: semver.clean(process.version),  // 提取程式版本資訊轉化成規定格式,也就是 '  =v1.2.3  ' -> '1.2.3' 這種功能
    versionRequirement: packageConfig.engines.node // package.json 的 node 的版本資訊
  }
]

if (shell.which('npm')) {
  versionRequirements.push({
    name: 'npm',
    currentVersion: exec('npm --version'),   //當前的版本資訊
    versionRequirement: packageConfig.engines.npm //package.json 的 node 的版本資訊
  })
}

module.exports = function () {
  const warnings = []

  for (let i = 0; i < versionRequirements.length; i++) {
    const mod = versionRequirements[i]

    // 如果當前版本號不符合 package.json 要求的版本號,紅色表示當前版本資訊,綠色表示要求的版本資訊,新增到 warnings 待輸出
    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
      warnings.push(mod.name + ': ' +
        chalk.red(mod.currentVersion) + ' should be ' +
        chalk.green(mod.versionRequirement)
      )
    }
  }

  //輸出版本號不相符的提示 warnings
  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.js

'use strict'

//打包前判斷當先開發環境的 node 和 npm 版本和 package.json 要求的時候一樣
require('./check-versions')()

process.env.NODE_ENV = 'production'

const ora = require('ora')  // 在使用者打包的時候能夠讓使用者知道正在進行,一個載入中的樣式,轉啊轉
const rm = require('rimraf') //這個模組是用來清除之前的打的包,因為在vue-cli中每次打包會生成不同的hash
const path = require('path') //node 路徑模組,便於我們操作檔案路徑
const chalk = require('chalk') //帶顏色的輸出模組,能在控制檯中輸出不同的樣色
const webpack = require('webpack') //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


    //開始打包,打包結束停止 spinner 轉圈,有報錯則在控制檯輸出
  webpack(webpackConfig, (err, stats) => {
    spinner.stop()
    if (err) throw err

    // node 環境裡的輸出配置,process.stdout.write 你可以理解成 js 裡的 console
    process.stdout.write(stats.toString({
      colors: true, //讓打包的時候有顏色。
      modules: false,  //去掉內建模組資訊
      children: false, // 去掉子模組,如果你使用了 ts-loader,設定成 true 會在打包構建階段展示錯誤資訊
      chunks: false, // 增加包資訊(設定為 false 能允許較少的冗長輸出)
      chunkModules: false //去除包裡內建模組的資訊
    }) + '\n\n')


     //打包出錯在控制檯輸出 Build failed with errors ,退出打包程式
    if (stats.hasErrors()) {
      console.log(chalk.red('  Build failed with errors.\n'))
      process.exit(1)
    }

    //打包成功則輸出 Build complete 結束打包
    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'
    ))
  })
})

webpack.base.conf.js

'use strict'
const path = require('path')  // node 路徑模組
const utils = require('./utils') //node 內部常用的工具類,其中包括:格式化字串、物件的序列化、實現物件繼承等常用方法
const config = require('../config') //?上面我們介紹的,專案配置檔案
const vueLoaderConfig = require('./vue-loader.conf') //? 上面我們介紹的 vue 載入器配置檔案

//返回當前配置檔案位置是 build ,該方法放回 build/../dir 的相對路基
function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

// eslint 語法檢測配置
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
  }
})

// webpack 通用配置內容
module.exports = {
  context: path.resolve(__dirname, '../'),  // 上下文,基礎目錄,用於從配置中解析入口起點和 loader
  entry: {
    app: './src/main.js'  //起點或是應用程式的起點入口。從這個起點開始,應用程式啟動執行。如果傳遞一個陣列,那麼陣列的每一項都會執行。
  },
  output: {
    path: config.build.assetsRoot,   //輸出 bundle 的路徑
    filename: '[name].js',          //輸出 bundle 的名稱
    publicPath: process.env.NODE_ENV === 'production' // 指定資原始檔引用的目錄,例如圖片
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'], //配置模組如何解析,
    alias: {                              // 建立應用的別名,
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },

module: {

rules: [
  //判斷配置中是否要是用 eslint 語法檢測,如果使用,就將 createLintingRule 配置物件返回
  ...(config.dev.useEslint ? [createLintingRule()] : []),

 //?是一些比較常用的載入器,及配置,不做詳細介紹了
  {
    test: /\.vue$/,
    loader: 'vue-loader',
    options: vueLoaderConfig
  },
  {
    test: /\.js$/,
    loader: 'babel-loader',
    include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
  },
  {
    test: /\.(css | scss)$/,
    loader: 'style-loader!css-loader!!sass-loader'
  },
  {
    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]')
    }
  }
]

},
node: {

//防止因為 vue 資源本身就自帶的 無用的 node 注入,瀏覽器相容處理
setImmediate: false,
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'

}
}

webpack.dev.conf.js

'use strict'
const utils = require('./utils')  //node 工具模組
const webpack = require('webpack') //webpack 不解釋
const config = require('../config')//?提到的配置檔案
const merge = require('webpack-merge') // merge 工具,用來合併生產和開發環境通用的基礎 webpack 配置
const path = require('path')            //node 的路徑模組
const baseWebpackConfig = require('./webpack.base.conf') //生產和開發環境通用的基礎 webpack 配置
const CopyWebpackPlugin = require('copy-webpack-plugin') //拷貝外掛
const HtmlWebpackPlugin = require('html-webpack-plugin')  //動態生成 html 外掛
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') //友好的錯誤輸出外掛
const portfinder = require('portfinder') //能夠獲取一個可用的隨機埠號

const HOST = process.env.HOST   //node 全域性環境變數的主機
const PORT = process.env.PORT && Number(process.env.PORT)   //node 全域性環境變數的埠

//合併基礎配置載入器的配置部分
const devWebpackConfig = merge(baseWebpackConfig, {

  module: {
    // 為 .vue 檔案意外的獨立樣式檔案配置載入器
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
  },

  // cheap-module-eval-source-map 在開發環境中很快
  devtool: config.dev.devtool,

  // 開發服務配置,? 已經細講過,順便回顧一下
  devServer: {
     clientLogLevel: 'warning',  // 在開發攻擊的控制檯中顯示資訊,便於開發除錯,你可以將引數配置成 "none" 來進行關閉
     historyApiFallback: { // 當使用 HTML5 History API 時,任意的 404 響應都可能需要被替代為 index.html
        rewrites: [
           { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
        ],
     },
     hot: true,   //啟用專案的熱重新整理,即模組熱替換特性
     contentBase: false,   // 告訴伺服器從哪裡提供內容。只有在你想要提供靜態檔案時才需要。這裡禁用,因為配置了 CopyWebpackPlugin 的使用
     compress: true,
     host: HOST || config.dev.host,   //指定使用一個域名。預設是 localhost
     port: PORT || config.dev.port,   //指定要監聽請求的埠號:
     open: config.dev.autoOpenBrowser, //open 引數配置,如果配置成 true ,專案啟動後會自動開啟瀏覽器
     overlay: config.dev.errorOverlay   //當有錯誤或則警告的時候在頁面上顯示一個全屏的遮罩提示
           ? { warnings: false, errors: true }
           : false,
     publicPath: config.dev.assetsPublicPath, //此路徑下的打包檔案可在瀏覽器中訪問
     proxy: config.dev.proxyTable,           //代理API的請求
     quiet: true,       //啟用 quiet 後,除了初始啟動資訊之外的任何內容都不會被列印到控制檯,特別是使用了 FriendlyErrorsPlugin 外掛的時候
     watchOptions: {   //與監視檔案相關的控制選項。是否使用輪詢
           poll: config.dev.poll,
     }
   },

  plugins: [
    // DefinePlugin 允許建立一個在編譯時可以配置的全域性常量。這可能會對開發模式和釋出模式的構建允許不同的行為非常有用
    new webpack.DefinePlugin({
      'process.env': require('../config/dev.env')
    }),
    new webpack.HotModuleReplacementPlugin(), //啟用熱替換模組(Hot Module Replacement),也被稱為 HMR
    new webpack.NamedModulesPlugin(), // 當開啟 HMR 的時候使用該外掛會顯示模組的相對路徑,建議用於開發環境
    new webpack.NoEmitOnErrorsPlugin(), 在編譯出現錯誤時,使用 NoEmitOnErrorsPlugin 來跳過輸出階段

    //HtmlWebpackPlugin簡化了HTML檔案的建立,以便為你的webpack包提供服務。這對於在檔名中包含每次會隨著編譯而發生變化雜湊的 webpack bundle 尤其有用
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),


    // 拷貝自定義的靜態資原始檔
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.dev.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

// 例項一個非同步物件,執行 devWebpackConfig 配置編譯
module.exports = new Promise((resolve, reject) => {
  portfinder.basePort = process.env.PORT || config.dev.port  //設定基礎埠
  portfinder.getPort((err, port) => {獲取埠,輸出構建新
    if (err) {
      reject(err)
    } else {
      // 如果進行 e2e 測試,需要釋出新埠
      process.env.PORT = port

      // 更新 devServer 的埠
      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)
    }
  })
})


webpack.prod.conf.js

'use strict'
const path = require('path') // node 路徑模組
const utils = require('./utils') //小工具函式
const webpack = require('webpack') // webpack 不解釋
const config = require('../config')//?提到的配置檔案
const merge = require('webpack-merge') // merge 工具,用來合併生產和開發環境通用的基礎 webpack 配置
const baseWebpackConfig = require('./webpack.base.conf')//產和開發環境通用的基礎 webpack 配置
const CopyWebpackPlugin = require('copy-webpack-plugin') //拷貝外掛
const HtmlWebpackPlugin = require('html-webpack-plugin')  //動態生成 html 外掛
const ExtractTextPlugin = require('extract-text-webpack-plugin')//用來做檔案分離的外掛
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')//優化提煉出來的css
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')// 壓縮 js 檔案外掛

//生產環境配置
const env = require('../config/prod.env')

//合併基礎配置載入器的配置部分
const webpackConfig = merge(baseWebpackConfig, {
//為獨立分離出來的樣式配置載入器和source,map
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true,
      usePostCSS: true
    })
  },
  //配置線上的 source map 便於排查問題
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  //配置輸出,路徑,檔名
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  plugins: [
    // DefinePlugin 允許建立一個在編譯時可以配置的全域性常量。這可能會對開發模式和釋出模式的構建允許不同的行為非常有用
    new webpack.DefinePlugin({
      'process.env': env
    }),

    // 使用 UglifyJsPlugin 外掛對 js 進行壓縮
    new UglifyJsPlugin({
      uglifyOptions: {
        compress: {
          warnings: false
        }
      },
      //配置外掛的source map
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),
    // 提取 css 到單獨的檔案,分離檔案非同步載入,提高載入速度
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),

      //如果把 allChunks 引數設定陳 false ,就不會把css 從程式碼塊中分離出來
      //程式碼塊載入的時候 css 會被 styles-loader 動態的載入
      allChunks: true,
    }),

    //使用這個外掛,從不同的元件中複製脫離出來,進行 css 壓縮
    new OptimizeCSSPlugin({
      cssProcessorOptions: config.build.productionSourceMap
        ? { safe: true, map: { inline: false } }
        : { safe: true }
    }),

    //自動生成 html 檔案,通常 index.html 檔案都會帶一個雜湊值來清除快取
    new HtmlWebpackPlugin({
      filename: config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
      },

      chunksSortMode: 'dependency'
    }),
    //該外掛會根據模組的相對路徑生成一個四位數的hash作為模組id, 渲染模組沒有變化的時候,id 不會變。
    new webpack.HashedModuleIdsPlugin(),

    // 提升或者預編譯所有模組到一個閉包中,提升你的程式碼在瀏覽器中的執行速度。
    new webpack.optimize.ModuleConcatenationPlugin(),

    // 分離渲染的js 到獨立的檔案中
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks (module) {
        //被引用到的包會從 node_modules 中提取出來
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),

    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),

    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),

    // 拷貝自定義的靜態資原始檔
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

//判斷如果配置了生產環境壓縮,是則使用外掛進行壓縮
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


擴充套件

?這篇文章詳細的介紹了腳手架專案的 webpack 配置,但是隻是 webpack 的一部分,還有很多內容值得我們去探究,如果你還感興趣,可以閱讀下面這些文章。也歡迎隨時與我進行交流,微訊號:646321933

webpack指南

webpack文件

webpack配置

自己動手實現一個腳手架

webpack 官方外掛集合介紹文件

《 使用vue-cli腳手架建立新專案》

相關文章