傻瓜版Webpack2.x版本示例項目
- 下載webpack-demo
項目目錄
├── app │ ├── app.js │ ├── app.scss │ ├── config.json │ ├── demo.js │ ├── demo.tmpl.html │ ├── greeter.js │ ├── icon.png │ ├── index.temp.html │ ├── jquery-1.11.3.min.js │ ├── main.js │ ├── main.scss ├── package.json └── webpack.config.js
- 運行
- npm install webpack -g 安裝webpack,運行webpack -h查看是否安裝成功
- npm install webpack-dev-server -g 安裝webpack-dev-server,可以通過一個socket.io服務實時監聽文件的變化並自動刷新頁面
- 安裝第三方npm模塊npm install
- 在根目錄下運行webpack-dev-server命令開啓本地服務
- 直接打開http://localhost:8080/測試
package.json
{ "name": "camera", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --progress --colors" }, "author": "vcxiaohan", "license": "ISC", "devDependencies": { "css-loader": "^0.26.4", "extract-text-webpack-plugin": "^2.1.0", "file-loader": "^0.10.1", "html-webpack-plugin": "^2.28.0", "json-loader": "^0.5.4", "node-sass": "^4.5.0", "sass-loader": "^6.0.3", "style-loader": "^0.13.2", "url-loader": "^0.5.8", "webpack": "^2.2.1" } }
webpack.config.js
var webpack = require('webpack'); var HtmlWebpackPlugin = require('html-webpack-plugin'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); // 必須配合html-webpack-plugin才能生效 module.exports = { devtool: 'source-map', //配置生成Source Maps,以便報錯時能定位到具體的行列 entry: { // 入口文件配置項 main: __dirname + "/app/main.js", // js文件標識,傳入HtmlWebpackPlugin的chunks參數位置,以便生成多個頁面時能引用不同的js文件 demo: __dirname + "/app/demo.js", // }, output: { // 輸出文件配置項 path: __dirname + "/public", // 打包後的文件存放的地方 filename: "[name].js" // 打包後輸出文件的文件名 }, module: { // 在配置文件裏添加JSON loader loaders: [{ test: /\.json$/, loader: "json-loader" // webpack2.x 版本不能省略loader後綴 }, { test: /\.scss$/, loader: ExtractTextPlugin.extract({ // extract-text-webpack-plugin2.x 版本寫法 fallback: 'style-loader', use: 'css-loader!sass-loader' }) }, { test: /\.(png|jpg)$/, loader: 'url-loader?limit=10&name=[hash].[ext]' // url-loader需要配合file-loader使用才能生效,否則讀取大圖片的時候會報錯 }, ] }, plugins: [ // 插件配置項 new HtmlWebpackPlugin({ // 生成多頁面 template: __dirname + "/app/index.tmpl.html", // 使用模板 filename: 'index.html', // 輸出的html文件名 chunks: ['main', 'common'], // 引用的js文件標識,必須要引入CommonsChunkPlugin獨立出來的common文件 }), new HtmlWebpackPlugin({ // 生成多頁面 template: __dirname + "/app/demo.tmpl.html", filename: 'demo.html', chunks: ['demo'], }), new webpack.BannerPlugin("by vcxiaohan"), //new webpack.optimize.UglifyJsPlugin(),// 壓縮js new ExtractTextPlugin("[name].css"), // 提取css樣式爲單獨的文件 new webpack.optimize.CommonsChunkPlugin({ // 把main文件標識的公共部分提取出來獨立成common文件 name: 'common', chunks: ['main'] }), ], devServer: { // 本地服務配置項 contentBase: "./public", // 自定義本地服務器基本目錄 inline: true, // 實時刷新 proxy: { // 代理 '/': { target: 'http://v4.faqrobot.net', changeOrigin: true // 解決跨域代理 } } } }
更新於2018-8-10
webpack3.12.0版本示例項目
- package.json
{
"name": "vue",
"version": "1.0.0",
"description": "A Vue.js project",
"author": "LAPTOP-ASHTD2FO\\Think <[email protected]>",
"private": true,
"scripts": {
"dll": "webpack --config webpack.dll.conf.js",
"dev": "webpack-dev-server --config webpack.conf.js",
"start": "npm run dev",
"unit": "jest --config test/unit/jest.conf.js --coverage",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
"build": "webpack --config webpack.conf.js"
},
"dependencies": {
"jquery": "^3.3.1",
"vue": "^2.5.2",
"vue-router": "^3.0.1"
},
"devDependencies": {
"add-asset-html-webpack-plugin": "^2.1.3",
"autoprefixer": "^7.1.2",
"babel-core": "^6.22.1",
"babel-eslint": "^8.2.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-jest": "^21.0.2",
"babel-loader": "^7.1.1",
"babel-plugin-dynamic-import-node": "^1.2.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-plugin-transform-vue-jsx": "^3.5.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.22.0",
"chalk": "^2.0.1",
"chromedriver": "^2.27.2",
"clean-webpack-plugin": "^0.1.19",
"copy-webpack-plugin": "^4.0.1",
"cross-spawn": "^5.0.1",
"css-hot-loader": "^1.4.1",
"css-loader": "^0.28.0",
"eslint": "^4.15.0",
"eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.7.1",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.2.0",
"eslint-plugin-promise": "^3.4.0",
"eslint-plugin-standard": "^3.0.1",
"eslint-plugin-vue": "^4.0.0",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^1.1.4",
"friendly-errors-webpack-plugin": "^1.6.1",
"happypack": "^5.0.0",
"html-webpack-plugin": "^2.30.1",
"jest": "^22.0.4",
"jest-serializer-vue": "^0.3.0",
"nightwatch": "^0.9.12",
"node-notifier": "^5.1.2",
"node-sass": "^4.9.3",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.2.0",
"portfinder": "^1.0.13",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.8",
"postcss-url": "^7.2.1",
"purify-css": "^1.2.5",
"purifycss-webpack": "^0.7.0",
"rimraf": "^2.6.0",
"sass-loader": "^7.1.0",
"selenium-server": "^3.0.1",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"style-loader": "^0.22.0",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^0.5.8",
"vue-jest": "^1.0.2",
"vue-loader": "^13.3.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.5.2",
"webpack": "^3.12.0",
"webpack-bundle-analyzer": "^2.9.0",
"webpack-dev-server": "^2.9.1",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
- webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const pkg = require('./package.json')
const library = '[name]_lib'
module.exports = {
entry: {
vendors: Object.keys(pkg.dependencies),
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].dll.js',
chunkFilename: '[name].chunk.js',
// DllPlugin插件需要該配置參數,用來生成manifest的映射名稱
library
},
plugins: [
// 該插件將第三方靜態資源單獨打包處理,避免我們在各種環境中重複打包永遠不會變化的文件,優化效果非常明顯(需要配合webpack內置插件DllReferencePlugin一起使用)
new webpack.DllPlugin({
// 打包後的文件路徑
path: path.resolve(__dirname, 'dist/[name]-manifest.json'),
name: library
}),
// 壓縮js文件,不要使用webpack內置的壓縮插件,因爲其版本低,緩存和多線程壓縮配置項都不生效,這裏使用獨立的壓縮插件還是有效的,同時該插件在設置babel的module爲false時默認開啓js tree shaking功能
new UglifyJsPlugin({
// 開啓緩存
cache: true,
// 開啓多線程並行處理
parallel: true,
}),
]
}
- webpack.conf.js
const path = require('path')
const webpack = require('webpack')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const glob = require('glob')
const PurifyCSSPlugin = require('purifycss-webpack')
const os = require('os')
const HappyPack = require('happypack')
const threadPool = HappyPack.ThreadPool({ size: os.cpus().length })
// 樣式loader配置文件,因爲有多處用到,故抽離成方法以便調用,一個ExtractTextPlugin實例是allInOne模式的,即所有的css合併成一個css文件,如果想抽離成多個文件,需要生成多個ExtractTextPlugin實例分別調用,如果想再優化的話,可以配合DllPlugin插件(這裏我偷個懶,沒有分開配置css和scss,請看我對css文件的正則,導致css和scss都會走這個方法,其實css是不需要sass-loader的)
function generateLoaders() {
return ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'sass-loader'],
})
}
module.exports = {
// source map配置項,由於打包後的bundle是一個文件,出現js錯誤後,不能定位到具體的模塊文件,開啓此項,幫我們解決此問題,但是不同的source map模式處理速度不同,建議不同環境,選擇不同的模式
// devtool: 'cheap-module-eval-source-map',
entry: {
app: './app.js',
// app2: './app2.js',
// app3: './app3.js',
// vendors: ['vue', 'jquery'],
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]-[hash:7].bundle.js',
chunkFilename: '[name].chunk.js'
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
// vue別名,我們在使用npm安裝vue的時候,會安裝vue的各種版本,適用於不同的環境,由於在webpack2.x版本中我們都是使用ES6 Module規範,如import、export等關鍵字,所以我們需要配置能遵循ES6 Module規範的那個vue版本
'vue$': 'vue/dist/vue.esm.js',
// 文件夾前綴別名,在引用某個模塊時,我們需要寫出絕對路徑,很麻煩,有了這個別名,我們可以把絕對路徑的前綴使用@來代替
'@': path.resolve(__dirname, './'),
}
},
module: {
rules: [
{
test: /\.vue$/,
use: {
loader: 'vue-loader',
options: {
loaders: {
/* // 這裏可以省略,因爲vue文件中的js會自動根據.babelrc文件的配置去處理
js: {
loader: 'babel-loader',
// options: '.babelrc文件內容拷貝至此亦可',
}, */
css: generateLoaders(),
scss: generateLoaders(),
}
}
},
}, {
test: /\.js$/,
/* use: {
// 將相應的id任務的loader交給happypack去處理,由於happypack可以開啓多個線程並行處理,可以優化速度,個人測試,效果並不理想,慎用,網上也看到很多人說使用happypack沒有效果,個人測試用例:對element-ui、jquery、vue、vue-router共4個模塊大約900kb,使用babel編譯,正常編譯和使用happypack編譯時間上並無什麼差別(happypack顯示開啓了4個線程,偶爾時間反而還會更多)
loader: 'happypack/loader?id=babel',
}, */
use: {
loader: 'babel-loader',
/* // 使用babel-loader插件時,如果我們想要編譯es6爲低版本瀏覽器能兼容的es5的話,我們需要爲該loader配置一些參數,而且每個需要用到babel-loader插件的地方,都要在相應的地方寫配置參數,比如我們要編譯.vue文件中的<script>塊的es6語法時,需要用到vue-loader,在vue-loader配置參數中我們依然要寫一遍babel-loader的配置參數,顯得很繁瑣,因此我們把要寫的配置參數統統提取出來放到.babelrc文件中,所有要用到babel的地方,在編譯時都會自動去查詢有沒有這個文件存在,而且慶幸的是這個文件支持JSON5格式,即我們在這個文件中可以隨心所欲的寫單引號、加單行、多行註釋、所有的鍵都可以不加雙引號,就像寫一個普通的js對象一樣,而不是寫一個格式要求嚴格的JSON文件
options: '.babelrc文件內容拷貝至此亦可', */
},
exclude: '/node_modules'
}, {// 開啓模塊熱更新後,style-loader會自動幫我們處理css模塊並實現模塊局部更新,但是由於我們又使用了ExtractTextPlugin插件抽離css樣式爲單獨的文件,所以這時css熱更新會失效(該插件缺少處理熱更新的api),當然我們可以不用ExtractTextPlugin插件,這樣一來我們引入的樣式文件都是以style標籤的形式插入html中,很不友好,爲此網上有人專門寫了一個CSS Hot Loader插件,使我們在使用ExtractTextPlugin插件的同時又能實現css模塊熱更新功能(總結:開發模式只需要熱更新,提高編譯速度,推薦使用寫法1,生產模式只需要提取css文件,推薦使用寫法2)
test: /\.s?css$/,
/* // css loader寫法1(此寫法需主動把plugins配置插件處的ExtractTextPlugin的代碼註釋)
// 最簡單的處理css的loader寫法,hot爲true時,css熱更新生效,但是不能抽離css爲單獨的樣式文件
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
], */
// css loader寫法2
// hot爲true時,css熱更新失效,但是能抽離css爲單獨的樣式文件
use: generateLoaders(),
/* // css loader寫法3(個人測試結果,該css-hot-loader的css熱更新效果並不理想,慎用)
// hot爲true時,css熱更新生效,同時能抽離css爲單獨的樣式文件
use: ['css-hot-loader'].concat(generateLoaders()), */
}, {
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
// 當圖片文件的大小比limit大時,會把文件交給file-loader去處理,反之,則自己處理並返回該文件的base64字符串,我個人認爲base64的利大於弊,在這裏我設爲-1,表示任何時候都不使用base64(base64優點:減少http請求、字符串可以使用gzip;缺點:一般圖片轉爲base64後,總字符串的體積會變大,不可讀,並且顯得冗餘,還會增加CSSOM解析時間,我比較傾向的是使用cssSprites的方案來合併小圖標)
limit: -1,
name: './dist/img/[name].[hash:7].[ext]',
}
},
]
},
plugins: [
/* // 打包結果可視化分析,該插件會自動打開一個窗口,展示打包後的結果,我們可以根據最終打包的模塊依賴做分析、優化工作
new BundleAnalyzerPlugin(), */
// 該插件將第三方靜態資源單獨打包處理,避免我們在各種環境中重複打包永遠不會變化的文件,優化效果非常明顯(需要配合webpack內置插件DllPlugin一起使用)
new webpack.DllReferencePlugin({
manifest: require('./dist/vendors-manifest.json')
}),
// 生成頁面插件,可以自定義模板、頁面title,寫多個即表示生成多頁面
new HtmlWebpackPlugin({
template: 'test.html',
}),
// 生成script標籤引用指定路徑,以實現自動添加靜態資源到頁面中,由於我們使用了DllReferencePlugin,所以我們需要手動把打包後的資源路徑添加到頁面中,而該插件幫我們自動完成
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, 'dist/*.dll.js'),
includeSourcemap: false,
// 文件名帶有隨機hash值,防止緩存,不能設置hash值長度,真的難受
hash: true,
}),
// 提取css樣式爲單獨的文件
new ExtractTextPlugin({
// 多入口時,必需使用name或contenthash值來確定打包出的css文件名,這樣提取出來的多個css文件就會以不同的名字命名,避免相互覆蓋
filename: '[name]-[contenthash:7].css',
allChunks: true,
}),
/* // 清理無用css,即css tree shaking,比如dom樹中壓根沒有這個節點,但是我們卻寫了這個節點的樣式,這個插件會幫我們除掉沒有用的css樣式,個人測試,效果並不理想,慎用
new PurifyCSSPlugin({
// 個人測試,只使用bootstrap的分頁樣式(其他的模塊也有問題),進行shaking後,css文件確實小了很多,但是展示的分頁樣式跟用shaking之前差了很多
paths: glob.sync(path.resolve(__dirname, './test.html')),
}), */
// 提取公用代碼,webpack的一個優化點,比如某個模塊我們在不同的入口分別引入了1次,那麼最終打包的多個bundle中都會有該模塊的代碼,增大了代碼體積,使用此插件我們可以從多個bundle中提取公用的模塊,減少代碼體積,但是注意提取過程需要時間,所以建議開發環境下開啓
new webpack.optimize.CommonsChunkPlugin({
// 提取出來的公用代碼的名稱
name: 'common',
// 配置幾個入口都引入該模塊時纔去提取,該值比較重要,決定了最終提取的公用代碼的體積和無用代碼量(無用代碼量:如果你設爲1,那麼只要某個模塊被某個入口引入了1次,該模塊就會被提取,但是其他的入口可能並不需要這個模塊,那麼就會成爲無用代碼)
minChunks: 2,
}),
// 注入全局變量,相當於使用別名來代替每次手動引入某個模塊,如以下,我們在文件中就不需要再手動書寫'import $ from \'jquery\''了
new webpack.ProvidePlugin({
$: 'jquery',
}),
/* // 該插件讓webpack能同時使用多個線程去處理loader,個人測試,效果並不理想,慎用
new HappyPack({
// 處理的任務id
id: 'babel',
loaders: [{
loader: 'babel-loader',
options: {
presets: [
['babel-preset-env', {
targets: {
browsers: ['last 2 versions']
}
}]
]
}
}],
// 使用的線程數
threadPool
}), */
// 在打包前對指定文件夾清理,由於我們加了hash值來命名文件,每次文件改動後,會重新生成新的文件,所以需要定時清理文件夾,開發環境不需要使用
new CleanWebpackPlugin(['dist'], {
// 不寫,也不會報錯
root: __dirname,
// 清理時,排除某些文件,比如我們的第三方依賴庫,從來沒有改變過,所以不需要清理
exclude: ['vendors.dll.js', 'vendors-manifest.json'],
}),
// hot爲true時,必須使用該插件熱更新才能生效
new webpack.HotModuleReplacementPlugin(),
// 熱更新時,輸出本次熱更新的模塊相對路徑
new webpack.NamedModulesPlugin(),
],
devServer: {// 當使用webpack-dev-server模塊來啓動項目時,該配置項生效,會開啓一個node服務器
// 開啓熱更新
hot: true,
// 路由錯誤處理,當我們訪問一個不存在的路由時,express會報404,該插件在出現此情況時,重定向到某個自己寫的頁面,更友好的提醒開發者
historyApiFallback: {
rewrites: [
{
from: /./,
to: '/404.html'
}
]
}
}
}
細節整理
- extract-text-webpack-plugin需要配合html-webpack-plugin使用才能生效
- url-loader需要配合file-loader使用才能生效,否則讀取大圖片的時候會報錯(最新版的url-loader已經內置了file-loader)
- CommonsChunkPlugin提取出來的文件,必須要在HtmlWebpackPlugin裏面引用,否則報錯webpackJsonp is not defined?
- Babel其實是幾個模塊化的包,其核心功能位於稱爲babel-core的npm包中,不過webpack把它們整合在一起使用,但是對於每一個你需要的功能或拓展,你都需要安裝單獨的包(用得最多的是解析Es6的babel-preset-es2015包和解析JSX的babel-preset-react包)。
參考文檔
- 入門Webpack,看這篇就夠了
- webpack官方文檔
- Webpack傻瓜式指南(一)
- Webpack傻瓜指南(二)開發和部署技巧
- 用webpack-dev-server開發時代理,決解開發時跨域問題
- 用webpack的CommonsChunkPlugin提取公共代碼的3種方式
- webpackJsonp is not defined?
- Vue + Webpack + Vue-loader 系列教程(1)功能介紹篇
- devDependencies和dependencies的區別
- babel的polyfill和runtime的區別
- webpack 配合babel 將es6轉成es5 超簡單實例
- Babel 全家桶
- webpack之babel插件困惑解疑
- Webpack2 升級指南和特性摘要
- html-webpack-plugin用法全解
- html-webpack-plugin 中使用 title選項設置模版中的值無效
- webpack多頁應用架構系列(七):開發環境、生產環境傻傻分不清楚?