最近爲了可以寫一份值得參考的webpack文檔,特地的去查了好多相應的書籍,博客。距離上次寫的那篇文章好想也過去將近一週的時間了。我想是時候要準備下一篇文章了。否則就食言而肥了。 算了,技術類文章就直接從技術類開始提及吧。首先,學習webpack呢?是由於我在開發vue和react的時候遇到了這個工具,而後最近在看人家的招聘要求的時候,老是會帶上這個。 javascript
而後我就趁着下班時間研究了一下這個東西。仍是和以前的說法同樣,若是有任何的疑惑,請在留言區留言,若是我能看見必定會及時的向您反饋。 新弄的github代碼地址 ###一、新語言的誕生背景 ![]()
近些年來web應用的功能需求的完善和所設計到領域愈加寬廣致使前端萌生了不少新的思想和框架。我簡單的介紹一下: 首先行業的領導着提出 模塊化的思想,他們認爲將一個複雜的系統分爲多個模塊來開發會大大減小開發難度和提高開發效率。同時考慮到css只能用靜態的語法描述元素的樣式,沒法像寫javascript那樣增長邏輯判斷與共享變量。因而在這種大環境下,誕生了 es6、 typescript、 scss等新的語言。可是考慮到例如es6沒法在瀏覽器中直接運行,須要將es6轉換成es5以後瀏覽器才能識別。因而新的構建工具就出現了。 構建工具的功能主要是:進行代碼轉換、文件壓縮、代碼分割、模塊合併、自動刷新、代碼檢驗、自動發佈等功能。構建實際上是工程化、自動化思想在前端開發的體驗,咱們要作的實際上是用代碼去讓前端項目自動化的執行這一系列化複雜的流程罷了。 ![]()
說到構建工具,我看百度上有不少。例如npm script、grunt、gulp、fis三、webpack等等。由於本文主要是講webpack,若是有機會能接觸到以上的幾個工具,我會另外詳細的描述。 ###二、 webpack的優點 ![]()
從我這些天不管是本身的實踐仍是書上寫的來講,我認爲webpack自己就是特別符合模塊化開發的一種工具。在webpack裏面一切文件都是模塊,並經過loader轉換文件,經過plugin注入鉤子,最後輸出由多模塊組合成的文件。其優勢主要是: 一、 專一於處理模塊化開發的項目,能作到開箱即用,一步到位 二、 能經過plugin擴展 三、 應用各類領域 四、 社區龐大 五、 良好的開發體驗 但談及爲何要選用webpack,我看書上主要有如下幾個見解: 一、大多數團隊在開發新項目的時候都會採用緊跟時代的技術,這些技術基本都會採用「模塊化+新語言+新框架」,webpack能夠爲這些新項目提供一站式的解決方案 二、webpack有良好的生態鏈和維護團隊,能提供必定的開發體驗並保證質量 三、webpack被全世界大量的web開發者使用和驗證,能找到各個層次面所需的教程和經驗分享 (反正,綜上所述 嗯 你再不學webpack就out了😂) ###三、 webpack的安裝(須要node環境滴) ![]()
初始化項目 css
npm init
複製代碼
全局安裝html
// 最新版本好像變成了webpack-cli注意一下
npm install -g webpack
複製代碼
項目內安裝前端
npm install webpack --save-dev
複製代碼
在這裏扯一句閒話,可能不少教程談到安裝webpack都會讓你選擇直接-g,可是我並不推薦你這麼作,我總感受-g以後就成了全局變量,可是我並非每一個項目內都能用到這個所謂的webpack,將webpack的做用域設置爲項目內,功能與全局沒有差異。 ###四、webpack的基礎使用 以下圖所示,在上面新建的項目下面新建index.js.。而後敲一些簡單的js代碼: vue
// 打包代碼
webpack-cli index.js --output build/bundle.js --mode development
複製代碼
打包成功以後就會發現目錄下面多了一個build目錄,裏面有bundle.js文件,最後新建index,html,並引用該js,就會看到以下的效果html5
cnpm install webpack-dev-server --save
複製代碼
// 兩步
webpack-cli
webpack-dev-server
複製代碼
cnpm install html-webpack-plugin --save
複製代碼
而後加入配置項 java
'use strict' // 嚴格模式
// Template version: 1.2.7
// see http://vuejs-templates.github.io/webpack for documentation.
const config = require('./config') // 導入config文件
const path = require('path') //使用Node自帶的文件路徑插件
module.exports = {
// 開發環境
dev: {
// Paths
assetsSubDirectory: 'static', // 編譯輸出的二級目錄
assetsPublicPath: '/', // 編譯發佈上線路徑的根目錄,可配置爲資源服務器域名或 CDN 域名
proxyTable: {
}, // 須要 proxyTable 代理的接口(可跨域),詳情請看以前的文章
// Various Dev Server settings
host: '0.0.0.0', // host,若是設置成0.0.0.0能夠經過統一局域網其餘設備經過ip訪問該網頁
port: 8080, // 網頁默認端口號,若是端口被佔用會自動分配一個隨即未被佔有的端口
autoOpenBrowser: false, // 是否自動打開瀏覽器
errorOverlay: true, // 在瀏覽器是否展現錯誤蒙層
notifyOnErrors: true, // 是否展現錯誤的通知
// 這個是webpack-dev-servr的watchOptions的一個選項,指定webpack檢查文件的方式
// 由於webpack使用文件系統去獲取文件改變的通知。在有些狀況下,這個可能不起做用。例如,當使用NFC的時候,
// vagrant也會在這方面存在不少問題,在這些狀況下,使用poll選項(以輪詢的方式去檢查文件是否改變)能夠設定爲true
// 或者具體的數值,指定文件查詢的具體週期。
poll: false,
// Use Eslint Loader?
useEslint: true,// eslint代碼檢查
showEslintErrorsInOverlay: false, // 若是設置爲true,在瀏覽器中,eslint的錯誤和警告會以蒙層的方式展示。
/**
* Source Maps
*/
// https://webpack.js.org/configuration/devtool/#development
devtool: 'eval-source-map', // 調試工具
cacheBusting: true, // 指定是否經過在文件名稱後面添加一個查詢字符串來建立source map的緩存
cssSourceMap: false, // 是否開啓 cssSourceMap
},
// 正式環境
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'), // 編譯註入的 index.html 文件,必須是本地的絕對路徑
// Paths
assetsRoot: path.resolve(__dirname, '../dist'), // 編譯輸出的靜態資源根路徑
assetsSubDirectory: 'static', // 編譯輸出的二級目錄
assetsPublicPath: config.getOutPutPath(), // 編譯發佈上線路徑的根目錄,可配置爲資源服務器域名或 CDN 域名
// assetsPublicPath: './',
/**
* Source Maps
*/
productionSourceMap: true, //生成用於生產構建的源映射
devtool: '#source-map', // 調試代碼的模式,共有7種,這裏是生成source-map文件
productionGzip: false, // 是否開啓 gzip
productionGzipExtensions: ['js', 'css'], // 須要使用 gzip 壓縮的文件擴展名
// 一個實用工具,用於分析項目的依賴關係
// 若是這個選項是true的話,那麼則會在build後,會在瀏覽器中生成一份bundler報告
bundleAnalyzerReport: process.env.npm_config_report
}
}
複製代碼
const path = require('path') // 引入nodejs的path模塊,用於操做路徑
const config = require('../config') // 引入模板的配置文件,下面就須要去這個文件中看看有什麼基本的配置
const ExtractTextPlugin = require('extract-text-webpack-plugin') // 提取特定文件的插件,好比把css文件提取到一個文件中去
const packageConfig = require('../package.json') // 加載package.json文件
// 生成編譯輸出的二級目錄
exports.assetsPath = function (_path) {
const assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
// path.posix是path模塊跨平臺的實現(不一樣平臺的路徑表示是不同的)
return path.posix.join(assetsSubDirectory, _path)
}
// 爲不一樣的css預處理器提供一個統一的生成方式,也就是統一處理各類css類型的打包問題。
// 這個是爲在vue文件中的style中使用的css類型
exports.cssLoaders = function (options) {
options = options || {}
// 打包css模塊
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
// 編譯postcss模塊
const postcssLoader = {
// 使用postcss-loader來打包postcss模塊
loader: 'postcss-loader',
// 配置source map
options: {
sourceMap: options.sourceMap
}
}
// 建立loader加載器字符串,結合extract text插件使用
/**
*
* loader:loader的名稱
* loaderOptions:loader對應的options配置對象
*/
function generateLoaders (loader, loaderOptions) {
// 經過usePostCSS 來標明是否使用了postcss
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
// 若是指定了具體的loader的名稱
if (loader) {
// 向loaders的數組中添加該loader對應的加載器
// 一個很重要的地方就是,一個數組中的loader加載器,是從右向左執行的。
loaders.push({
// loader加載器的名稱
loader: loader + '-loader',
// 對應的加載器的配置對象
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// 若是明確指定了須要提取靜態文件,則使用
// ExtractTextPlugin.extract({})來包裹咱們的各類css處理器。
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
// fallback這個選項咱們能夠這樣理解
// webpack默認會按照loaders中的加載器從右向左調用編譯各類css類型文件。若是一切順利,在loaders中的
// 各個加載器運行結束以後就會把css文件導入到規定的文件中去,若是不順利,則繼續使用vue-style-loader來處理
// css文件
fallback: 'vue-style-loader'
})
} else {
// 若是沒有提取行爲,則最後再使用vue-style-loader處理css
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'),
styl: generateLoaders('stylus')
}
}
// 使用這個函數,爲那些獨立的style文件建立加載器配置。
exports.styleLoaders = function (options) {
// 保存加載器配置的變量
const output = []
// 獲取全部css文件類型的loaders
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
// 生成對應的loader配置
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
exports.createNotifierCallback = () => {
// node-notifier是一個跨平臺的包,以相似瀏覽器的通知的形式展現信息。
const notifier = require('node-notifier')
return (severity, errors) => {
// 只展現錯誤的信息
if (severity !== 'error') return
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
// 須要展現的錯誤信息的內容
notifier.notify({
// 通知的標題
title: packageConfig.name,
// 通知的主體內容
message: severity + ': ' + error.name,
// 副標題
subtitle: filename || '',
// 通知展現的icon
icon: path.join(__dirname, 'logo.png')
})
}
}
複製代碼
const utils = require('./utils')
const config = require('../config')
// 設置是否是生產環境
const isProduction = process.env.NODE_ENV === 'production'
// 根據不一樣的環境,引入不一樣的source map配置文件
const sourceMapEnabled = isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap
module.exports = {
// vue文件中的css loader配置
loaders: utils.cssLoaders({
sourceMap: sourceMapEnabled,
// 生產環境下就會把css文件抽取到一個獨立的文件中
extract: isProduction
}),
// css source map文件的配置
cssSourceMap: sourceMapEnabled,
// css source map文件緩存控制變量
cacheBusting: config.dev.cacheBusting,
transformToRequire: {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href'
}
}
複製代碼
const path = require('path') // 使用 NodeJS 自帶的文件路徑插件
const utils = require('./utils') //封裝了一些方法的工具
const config = require('../config') //使用 config/index.js
const vueLoaderConfig = require('./vue-loader.conf') //使用vue-loader.conf
function resolve (dir) {
return path.join(__dirname, '..', dir) // 拼接咱們的工做區路徑爲一個絕對路徑
}
// eslint的規則
const createLintingRule = () => ({
// 對.js和.vue結尾的文件進行eslint檢查
test: /\.(js|vue)$/,
// 使用eslint-loader
loader: 'eslint-loader',
// enforce的值多是pre和post。其中pre有點和webpack@1中的preLoader配置含義類似。
// post和v1中的postLoader配置含義類似。表示loader的調用時機
// 這裏表示在調用其餘loader以前須要先調用這個規則進行代碼風格的檢查
enforce: 'pre',
// 須要進行eslint檢查的文件的目錄存在的地方
include: [resolve('src'), resolve('test')],
// eslint-loader配置過程當中須要指定的選項
options: {
// 文件風格的檢查的格式化程序,這裏使用的是第三方的eslint-friendly-formatter
formatter: require('eslint-friendly-formatter'),
// 是否須要eslint輸出警告信息
emitWarning: !config.dev.showEslintErrorsInOverlay
}
})
var webpack = require('webpack')
module.exports = {
// webpack在尋找尋找相對路徑的文件時候會以context做爲根目錄。
// context默認爲執行啓動webpack時所在的當前工做目錄
context: path.resolve(__dirname, '../'),
// entry表示入口,webpack構建的第一步從entry開始
// 類型能夠是string,object,array
entry: {
app: './src/main.js'
},
output: {
// 輸出文件存放的目錄,必須是string類型的絕對目錄
path: config.build.assetsRoot,
// 經過entry不一樣生成不一樣的文件名字,詳情請看文章
filename: '[name].js',
// 發佈到線上全部資源的url前綴,爲string類型
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath,
// 導出庫的名稱,爲string類型,不填寫的時候,默認輸出格式是匿名的當即執行函數
// library: "KlivitamLibrary"
// 導出庫的類型,爲枚舉類型,默認是var
// 可選值: umd umd2 commonjs2,commonjs,amd,this,var,assign,window,global,jsonp
// libraryTarget: "jsonp"
// 是否包含游泳的文件信息到生成的代碼裏
// pathinfo: true
// 附加chunk的文件名稱
// chunkFilename: "[id].js"
// chunkFilename: "[chunkhash].js"
// jsonp異步加載資源時的回調函數名
// jsonpFunction: "webpackJsonP"
// 生成source map文件的名稱
// sourceMapFilename: "[file].map"
//瀏覽器開發者工具裏顯示的遠嗎模塊名稱
// devtoolModuleFilenameTemplate: "webpack:///[resource-path]"
// 異步加載跨域的資源時使用的方式
// crossOriginLoading: "use-credentials",
// crossOriginLoading: "anonymous",
// crossOriginLoading: false,
},
// 配置模塊解析時候的一些選項
resolve: {
// 自動補全的擴展名,可以使用戶在引入模塊時不帶擴展
extensions: ['.js', '.vue', '.json'],
// 默認路徑代理,例如 import Vue from 'vue$',會自動到 'vue/dist/vue.esm.js'中尋找
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'components': resolve('src/components'),
// 能夠在引入文件的時候使用pages符號引入src/pages文件夾中的文件
'pages': resolve('src/pages'),
'http': resolve('src/http'),
'public': resolve('src/public'),
'jquery': 'jquery'
}
},
// 下面是針對具體的模塊進行的具體的配置
// 下面的配置語法採用的是version >= @2的版本
module: {
noParse: [/videojs-contrib-hls/], // 不用解析和處理的模塊
// rules是一個數組,其中的每個元素都是一個對象,這個對象是針對具體類型的文件進行的配置。
rules: [
...(config.dev.useEslint ? [createLintingRule()] : []),
{
test: /\.vue$/, // 正則匹配loader名字
loader: 'vue-loader', // loader名字
// 針對此加載器的具體配置
// 針對前面的分析,這個配置對象中包含了各類css類型文件的配置,css source map的配置 以及一些transform的配置
options: vueLoaderConfig
},
{
test: /\.js$/,
// js文件的處理主要使用的是babel-loader。在這裏沒有指定具體的編譯規則,babel-loader會自動
// 讀取根目錄下面的.babelrc中的babel配置用於編譯js文件
loader: 'babel-loader',
// 指定須要進行編譯的文件的路徑
// 這裏表示只對src和test文件夾中的文件進行編譯
include: [resolve('src'), resolve('test'), resolve('config/myapi')] //規則所包含的文件夾
},
{
// 對圖片資源進行編譯的配置
// 指定文件的類型
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
// 使用url-loader進行文件資源的編譯
loader: 'url-loader',
// url-loader的配置選項
options: {
// 文件的大小小於10000字節(10kb)的時候會返回一個dataUrl
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]')
}
}
]
},
// 這些選項用於配置polyfill或mock某些node.js全局變量和模塊。
// 這可使最初爲nodejs編寫的代碼能夠在瀏覽器端運行
node: {
// 這個配置是一個對象,其中的每一個屬性都是nodejs全局變量或模塊的名稱
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
// 設置成empty則表示提供一個空對象
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}
複製代碼
+build/webpack.dev.conf.jsnode
'use strict'
// 首先引入的是一些工具方法,下面咱們就須要去util文件種看一下有哪些對應的工具方法
const utils = require('./utils')
// 引入webpack模塊
const webpack = require('webpack')
// 引入配置文件
// 這個配置文件中包含了一些dev和production環境的基本配置
const config = require('../config')
// 引入webpack-merge模塊。這個模塊用於把多個webpack配置合併成一個配置,後面的配置會覆蓋前面的配置。
const merge = require('webpack-merge')
// 引入webpack的基本設置,這個設置文件包含了開發環境和生產環境的一些公共配置
const baseWebpackConfig = require('./webpack.base.conf')
// 用於生成html文件的插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 這個插件可以更好的在終端看到webpack運行時的錯誤和警告等信息。能夠提高開發體驗
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// 查找一個未使用的端口
const portfinder = require('portfinder')
// 獲取host環境變量,用於配置開發環境域名
const HOST = process.env.HOST
// 獲取post環境變量,用於配置開發環境時候的端口號
const PORT = process.env.PORT && Number(process.env.PORT)
// 開發環境的完整的配置文件
const devWebpackConfig = merge(baseWebpackConfig, {
module: {
// 爲那些獨立的css類型文件添加loader配置(沒有寫在vue文件的style標籤中的樣式)
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
// 開發環境使用'eval-source-map'模式的source map
// 由於速度快
devtool: config.dev.devtool,
// 下面是對webpack-dev-server選項的基本配置,這些配置信息,咱們能夠在/config/index.js
// 文件中進行自定義配置。
devServer: {
// 用於配置在開發工具的控制檯中顯示的日誌級別
// 注意這個不是對bundle的錯誤和警告的配置,而是對它生成以前的消息的配置
clientLogLevel: 'warning',
// 表示當使用html5的history api的時候,任意的404響應都須要被替代爲index.html
historyApiFallback: true,
// 啓用webpack的熱替換特性
hot: true,
// 一切服務都須要使用gzip壓縮
// 能夠在js,css等文件的response header中發現有Content-Encoding:gzip響應頭
compress: true,
// 指定使用一個 host。默認是 localhost
// 若是但願服務器外部能夠訪問(經過咱們電腦的ip地址和端口號訪問咱們的應用)
// 能夠指定0.0.0.0,使用這個可使得同一局域網內全部設備都能訪問你的本地網頁
host: HOST || config.dev.host,
// 指定要監聽請求的端口號
port: PORT || config.dev.port,
// 是否自動打開瀏覽器
open: config.dev.autoOpenBrowser,
// 當編譯出現錯誤的時候,是否但願在瀏覽器中展現一個全屏的蒙層來展現錯誤信息
overlay: config.dev.errorOverlay
// 表示只顯示錯誤信息而不顯示警告信息
// 若是二者都但願顯示,則把這兩項都設置爲true
? { warnings: false, errors: true }
// 設置爲false則表示啥都不顯示
: false,
// 指定webpack-dev-server的根目錄,這個目錄下的全部的文件都是能直接經過瀏覽器訪問的
// 推薦和output.publicPath設置爲一致
publicPath: config.dev.assetsPublicPath,
// 配置代理,這樣咱們就能夠跨域訪問某些接口
// 咱們訪問的接口,若是符合這個選項的配置,就會經過代理服務器轉發咱們的請求
proxy: config.dev.proxyTable,
// 啓用 quiet 後,除了初始啓動信息以外的任何內容都不會被打印到控制檯。這也意味着來自 webpack 的錯誤或警告在控制檯不可見。
quiet: true, // necessary for FriendlyErrorsPlugin
// 與監視文件相關的控制選項
watchOptions: {
// 若是這個選項爲true,會以輪詢的方式檢查咱們的文件的變更,效率很差
poll: config.dev.poll,
}
},
plugins: [
// 建立一個在編譯時能夠配置的全局變量
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
// 啓用熱替換模塊
// 記住,咱們永遠不要再生產環境中使用hmr
new webpack.HotModuleReplacementPlugin(),
// 這個插件的主要做用就是在熱加載的時候直接返回更新文件的名稱,而不是文件的id
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
// 使用這個插件能夠在編譯出錯的時候來跳過輸出階段,這樣能夠確保輸出資源不會包含錯誤。
new webpack.NoEmitOnErrorsPlugin(),
// 這個插件主要是生成一個html文件
new HtmlWebpackPlugin({
// 生成的html文件的名稱
filename: 'index.html',
// 使用的模板的名稱
template: 'index.html',
// 將全部的靜態文件都插入到body文件的末尾
inject: true
}),
]
})
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
// 這種獲取port的方式會返回一個promise
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// 把獲取到的端口號設置爲環境變量PORT的值
process.env.PORT = port
// 從新設置webpack-dev-server的端口的值
devWebpackConfig.devServer.port = port
// 將FriendlyErrorsPlugin添加到webpack的配置文件中
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
// 編譯成功時候的輸出信息
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
// 當編譯出錯的時候,根據config.dev.notifyOnErrors來肯定是否須要在桌面右上角顯示錯誤通知框
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
// resolve咱們的配置文件
resolve(devWebpackConfig)
}
})
})
複製代碼
// 引入path模塊
const path = require('path')
// 引入工具方法
const utils = require('./utils')
// 引入webpack模塊
const webpack = require('webpack')
// 引入基本的配置
const config = require('../config')
// 引入webpack-merge模塊
const merge = require('webpack-merge')
// 引入開發環境和生產環境公共的配置
const baseWebpackConfig = require('./webpack.base.conf')
// 這個模塊主要用於在webpack中拷貝文件和文件夾
const CopyWebpackPlugin = require('copy-webpack-plugin')
// 這個插件主要是用於基於模版生成html文件的
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 這個插件主要是用於將入口中全部的chunk,移到獨立的分離的css文件中
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// 這個插件主要是用於壓縮css模塊的
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
// 這個插件主要是用於壓縮js文件的
// const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
// 配置多份環境
let env
if(process.argv[2] === 'test'){
env=require('../config/dev.env')
}else{
env=require('../config/prod.env')
}
// 合併公共配置和生產環境獨有的配置並返回一個用於生產環境的webpack配置文件
const webpackConfig = merge(baseWebpackConfig, {
// 用於生產環境的一些loader配置
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
// 在生產環境中使用extract選項,這樣就會把thunk中的css代碼抽離到一份獨立的css文件中去
extract: true,
usePostCSS: true
})
},
// 配置生產環境中使用的source map的形式。在這裏,生產環境使用的是#source map的形式
devtool: config.build.productionSourceMap ? config.build.devtool : false,
output: {
// build所產生的文件的存放的文件夾地址
path: config.build.assetsRoot,
// build以後的文件的名稱
// 這裏[name]和[chunkhash]都是佔位符
// 其中[name]指的就是模塊的名稱
// [chunkhash]chunk內容的hash字符串,長度爲20
filename: utils.assetsPath('js/[name].[chunkhash].js'),
// [id]也是一個佔位符,表示的是模塊標識符(module identifier)
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
// 壓縮javascript的插件
new webpack.optimize.UglifyJsPlugin({
// 壓縮js的時候的一些基本配置
uglifyOptions: {
// 配置壓縮的行爲
compress: {
// 在刪除未使用的變量等時,顯示警告信息,默認就是false
warnings: false
}
},
// 使用 source map 將錯誤信息的位置映射到模塊(這會減慢編譯的速度)
// 並且這裏不能使用cheap-source-map
sourceMap: config.build.productionSourceMap,
// 使用多進程並行運行和文件緩存來提升構建速度
parallel: true
}),
// 提取css文件到一個獨立的文件中去
new ExtractTextPlugin({
// 提取以後css文件存放的地方
// 其中[name]和[contenthash]都是佔位符
// [name]就是指模塊的名稱
// [contenthash]根據提取文件的內容生成的 hash
filename: utils.assetsPath('css/[name].[contenthash].css'),
// 從全部額外的 chunk(additional chunk) 提取css內容
// (默認狀況下,它僅從初始chunk(initial chunk) 中提取)
// 當使用 CommonsChunkPlugin 而且在公共 chunk 中有提取的 chunk(來自ExtractTextPlugin.extract)時
// 這個選項須要設置爲true
allChunks: true,
}),
// duplicated CSS from different components can be deduped.
// 使用這個插件壓縮css,主要是由於,對於不一樣組件中相同的css能夠剔除一部分
new OptimizeCSSPlugin({
// 這個選項的全部配置都會傳遞給cssProcessor
// cssProcessor使用這些選項決定壓縮的行爲
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
}),
// 建立一個html文件
new HtmlWebpackPlugin({
// 建立一個html文件
filename: config.build.index,
// 使用的模板的名稱
template: 'index.html',
// 把script和link標籤放在body底部
inject: true,
// 配置html的壓縮行爲
minify: {
// 移除註釋
removeComments: true,
// 去除空格和換行
collapseWhitespace: true,
// 儘量移除屬性中的引號和空屬性
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// chunks 目錄
chunks: ['manifest', 'vendor', 'app'],
// 控制chunks的順序,這裏表示按照依賴關係進行排序
// 也能夠是一個函數,本身定義排序規則
chunksSortMode: 'dependency'
}),
// 根據模塊的相對路徑生成一個四位數的hash做爲模塊id
new webpack.HashedModuleIdsPlugin(),
// webpack2處理過的每個模塊都會使用一個函數進行包裹
// 這樣會帶來一個問題:下降瀏覽器中JS執行效率,這主要是閉包函數下降了JS引擎解析速度。
// webpack3中,經過下面這個插件就可以將一些有聯繫的模塊,
// 放到一個閉包函數裏面去,經過減小閉包函數數量從而加快JS的執行速度。
new webpack.optimize.ModuleConcatenationPlugin(),
// 這個插件用於提取多入口chunk的公共模塊
// 經過將公共模塊提取出來以後,最終合成的文件可以在最開始的時候加載一次
// 而後緩存起來供後續使用,這會帶來速度上的提高。
new webpack.optimize.CommonsChunkPlugin({
// 這是 common chunk 的名稱
name: 'vendor',
// 把全部從mnode_modules中引入的文件提取到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
)
}
}),
// 爲了將項目中的第三方依賴代碼抽離出來,官方文檔上推薦使用這個插件,當咱們在項目裏實際使用以後,
// 發現一旦更改了 app.js 內的代碼,vendor.js 的 hash 也會改變,那麼下次上線時,
// 用戶仍然須要從新下載 vendor.js 與 app.js——這樣就失去了緩存的意義了。因此第二次new就是解決這個問題的
// 參考:https://github.com/DDFE/DDFE-blog/issues/10
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
// 拷貝靜態資源到build文件夾中
new CopyWebpackPlugin([
{
// 定義要拷貝的資源的源目錄
from: path.resolve(__dirname, '../static'),
// 定義要拷貝的資源的目標目錄
to: config.build.assetsSubDirectory,
// 忽略拷貝指定的文件,可使用模糊匹配
ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {
// 若是開啓了生產環境的gzip
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
// 目標資源的名稱
// [path]會被替換成原資源路徑
// [query]會被替換成原查詢字符串
asset: '[path].gz[query]',
// gzip算法
// 這個選項能夠配置成zlib模塊中的各個算法
// 也能夠是(buffer, cb) => cb(buffer)
algorithm: 'gzip',
// 處理全部匹配此正則表達式的資源
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
// 只處理比這個值大的資源
threshold: 10240,
// 只有壓縮率比這個值小的資源纔會被處理
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
// 若是須要生成一分bundle報告,則須要使用下面的這個插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
複製代碼
// 在終端爲不一樣字體顯示不一樣的風格
const chalk = require('chalk')
// 解析npm包的version
const semver = require('semver')
// 引入package.json文件
const packageConfig = require('../package.json')
// node版本的uninx shell命令
const shell = require('shelljs')
// 執行命令的函數
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
const versionRequirements = [
{
name: 'node',
// node的版本
// process.version就是node的版本
// semver.clean('v8.8.0') => 8.8.0
currentVersion: semver.clean(process.version),
// package.json中定義的node版本的範圍
versionRequirement: packageConfig.engines.node
}
]
// 至關於 which npm
if (shell.which('npm')) {
// 若是npm命令存在的話
versionRequirements.push({
name: 'npm',
// 檢查npm的版本
currentVersion: exec('npm --version'),
// package.json中定義的npm版本
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
const warnings = []
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]
// semver.satisfies()進行版本之間的比較
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
// 若是現有的npm或者node的版本比定義的版本低,則生成一段警告
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)
}
}
複製代碼
'use strict'
require('./check-versions')() // 檢查npm和node的版本
process.env.NODE_ENV = 'production' // 設置環境變量NODE_ENV的值是production
const ora = require('ora') // 終端的spinner
const rm = require('rimraf') // node.js版本的rm -rf
const path = require('path') // 使用Node自帶的文件路徑工具
const chalk = require('chalk') // 引入顯示終端顏色模塊
// const opn = require('opn') // 一個能夠強制打開瀏覽器並跳轉到指定url的插件
const webpack = require('webpack') // 使用webpack
const config = require('../config') // 加載config的配置
const webpackConfig = require('./webpack.prod.conf') // 引入webpack在production環境下的配置文件
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' )) }) }) 複製代碼
###六、最後說兩句react