這不是一個純粹的學習帖子,最開始爲了生產項目考慮的。公司有個新的、小的活動項目。以此爲假想,因此我但願學習一些新的技術應用在上面;這個新的項目是做爲舊項目的一個子系統存在的,因此又必須在必定程度上保持一致。
而這個舊項目的原有使用構建工具fis的版本比較老舊,不敢升級,怕出什麼幺蛾子,因此又不能動他。
在網上學習了衆多攻略以後本身嘗試搭建了一下,解決了一些問題,也留下了一下疑惑。javascript
github: github.com/johnshere/a…css
node: v10.6.0(v6.12.3)、yarn: 1.7.0、webpack: 4.16.1
系統:windows10html
yarn是相似npm可是效率更高的包管理工具,命令互換參考yarnpkg.com/zh-Hans/doc…
可使用npm安裝(這裏讓我想到IE存在的意義,-_-)java
npm install -g yarn
複製代碼
也能夠去官網下載客戶端node
如圖: jquery
src:工程源代碼; release:工程發佈; webpack.config:webpack配置文件;webpack
發佈以後的HTML保持與src中的路徑一致;這樣代碼中使用相對路徑訪問頁面就不會出現結構錯亂的問題。nginx
src目錄下有三個文:entry.json/index.html/index.js; 公司原有的fis是最第一版本的,一直沒有人作更新維護,如今已經落後如今的技術版本多年,可是又必須使用。
webpack4不可能在和他進行兼容,因此我安裝了兩個不一樣版本的node,v10.6.0、v6.12.3;使用的時候切換 git
set dir=D:
set name1=node612
set name2=node106
set name=node
if exist %dir%\%name1% (
echo "node612 ==> node"
ren %dir%\%name% %name2%
ren %dir%\%name1% %name%
)else (
echo "node106 ==> node"
ren %dir%\%name% %name1%
ren %dir%\%name2% %name%
)
pause
複製代碼
這樣切換了node,實際上就是切換整個開發環境,畢竟這兩個構建工具都是依賴於node的。
切換時在cmd或者powershell裏執行:github
changeNodeName
這個應該是重中之重了,在寫配置以前我首先肯定了本身想解決的一些問題
- 發佈後保證目錄結構不變
- 分割公共文件,如樣式、圖片;達到緩存目的
- 分割的大文件不能過大(未解決)、不能讓用戶頻繁加載
- 保證文件之間緩存良好互不干擾
- 轉義語法
const path = require("path");
const Glob = require("glob");
const fs = require("fs");
let obj = {
/**
* 根據目錄獲取入口
* @param {[type]} globPath [description]
* @return {[type]} [description]
*/
getEntryJs: function (globPath) {
globPath = path.resolve(__dirname, globPath);
let entries = {};
Glob.sync(globPath).forEach(function (entry) {
let basename = path.basename(entry, path.extname(entry)),
pathname = path.dirname(entry),
paths = pathname.split('/'),
fileDir = paths.splice(paths.indexOf("src") + 1).join('/');
//僅處理page路徑下的js
if (pathname.indexOf("page") > -1) {// && fileDir && fileDir.indexOf(("page") === 0)) {
entries[(fileDir ? fileDir + '/' : fileDir) + basename] = pathname + '/' + basename;
}
});
//目錄頁保留
entries["index"] = path.resolve(__dirname,"../src/index").split("\\").join("/");
console.log("---------------------------------------------\nentries:");
console.log(entries);
console.log("----------------------------------------------");
return entries;
},
/**
* 根據目錄獲取 Html 入口
* @param {[type]} globPath [description]
* @return {[type]} [description]
*/
getEntryHtml: function (globPath) {
globPath = path.resolve(__dirname, globPath);
let entries = [];
Glob.sync(globPath).forEach(function (entry) {
let basename = path.basename(entry, path.extname(entry)),
pathname = path.dirname(entry),
paths = pathname.split('/'),
// @see https://github.com/kangax/html-minifier#options-quick-reference
minifyConfig = process.env.NODE_ENV === "production" ? {
removeComments: true,
// collapseWhitespace: true,
minifyCSS: true,
minifyJS: true
} : "";
//只處理page目錄下的HTML
//保留目錄頁
if (entry.indexOf("page") > -1 ) {
let chunkName = paths.splice(paths.indexOf("src") + 1).join('/') + "/" + basename;
entries.push({
filename: chunkName + ".html",
template: entry,
chunks: ['public/vendor', chunkName],
minify: minifyConfig
});
}
});
//保留目錄頁
entries.push({
filename: "index.html",
template: path.resolve(__dirname,"../src/index.html").split("\\").join("/"),
chunks: ['public/vendor',"index"]
});
//保存entry的json文件
this.entry2JsonFile(entries);
return entries;
},
/**
* 生成entry對應的json文件
* @param entries
*/
entry2JsonFile: function (entries) {
console.log(entries);
let json = {};
if (entries) {
entries.forEach(v => {
json[v.filename] = v.filename;
});
}
console.log(json);
//同步寫入文件
let fd = fs.openSync(path.resolve(__dirname, "../src/entry.json"), "w");
fs.writeSync(fd, JSON.stringify(json), 0, "utf-8");
fs.closeSync(fd);
}
};
// obj.getEntry("../src/page/**/*.js");
// obj.getEntryHtml('../src/page/**/index.html');
module.exports = obj;
複製代碼
這個地方的entry識別參考了:
github地址:github.com/givebest/we…
這個entry工具主要是爲了識別js和HTML;我在原有的邏輯上進行了修改,符合了個人要求,即只識別page目錄下的entry。
同時,我添加了一個方法,即將全部的HTML路徑寫入到一個json文件中保存起來(後面dev-server模式用到)。前兩個方法裏也爲入口目錄頁作了特殊處理 這個工具中對chunk的key值作了特殊處理,能夠看出,切割出了從src以後的路徑做爲key值,由於webpack的name是支持路徑的,這樣就達到問題1的效果。
const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
// const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CleanWebpackPlugin = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const entryUtil = require("./webpack.entry.util");
let entryJs = entryUtil.getEntryJs('../src/page/**/index.js');
let conf = {
entry: entryJs,//js打包入口識別
output: {
path: path.resolve(__dirname, "../release"),
filename: "[name].[chunkHash].js",
// publicPath: "../../public"
},
module: {
rules: [
{
test: /\.css$/,
// loader: ExtractTextPlugin.extract({
// fallback: 'style-loader',
// use: 'css-loader'
// })
use:[MiniCssExtractPlugin.loader,'css-loader']//'style-loader',
},
{
test: /\.html$/,
loader: 'html-withimg-loader'
},
{test: require.resolve("jquery"), loader: "expose-loader?$!expose-loader?jQuery"}
]
},
plugins: [
// new HtmlWebpackPlugin({
// filename: "index.html",
// template: "src/page/index.html",
// chunks: ["main", "vender"]
// }),
// new ExtractTextPlugin("./[name].[chunkHash].css")
new CleanWebpackPlugin(["release"],{
root: path.resolve(__dirname, ".."),
verbose: true,
dry: false
}),
new MiniCssExtractPlugin({
filename: "[name].[contenthash:7].css",
chunkFilename: "[name].[contenthash].css"
})
],
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: "public/vendor",
chunks: "all",
minChunks: 2
}
}
}
},
resolve: {
extensions: [".js", ".jsx"],
alias: {
layer: path.resolve(__dirname, "../src/public/js/layer/mobile/layer.js"),
"layer.css": path.resolve(__dirname, "../src/public/js/layer/mobile/need/layer.css")
}
}
};
//HTML入口
let entryHtml = entryUtil.getEntryHtml('../src/page/**/index.html');
entryHtml.forEach(function (v) {
conf.plugins.push(new HtmlWebpackPlugin(v));
});
module.exports = conf;
複製代碼
這裏就須要給解釋了,開始學習webpack,而後網上不斷找各類帖子,學習、修改、測試最終成了這些配置文件,有些改動時間長了我本身都忘記(-_-)!。
使用工具獲取指定的HTML和js,這裏我作一個限制,只取index名稱的,這是由於公司不少模板文件都是用html後綴。
webpack的入口是隻識別js的,這裏就須要用到HtmlWebpackPlugin,沒生成一個HTML與js的對應關係就要new一個HtmlWebpackPlugin。因此上面entryHtml是push進去的,還有就是entryHtml中作了生產環境的判斷。
如今使用的是MiniCssExtractPlugin,可是從註釋開出來我最開始使用的是ExtractTextPlugin(我也是從註釋看到纔想起來的,哈哈哈哈)。
先說ExtractTextPlugin,這個要在webpack4上面用正常安裝是不行的,如今必須指定版本@next,不然不能兼容webpack4。以下:
yarn add ExtractTextPlugin@next
複製代碼
配置好了以後,我用了一段時間,最後在思考上面第四個問題的時候,把這個替換掉了,ExtractTextPlugin好像不能使用contenthash。
咱們公司是作bss系統的,業務複雜,並且更換業務邏輯的頻率很快,因此index.js修改比較多,可是樣式和圖片其實改動很少,不能由於改了一個if else,就須要用戶更新css和圖片吧。因此換成MiniCssExtractPlugin如今的樣子。
而後關於MiniCssExtractPlugin的配置
filename是配置每一個chunk對應分割出css文件的配置
chunkfilename是配置分離出的公共css文件的配置
jquery沒有實現模塊化,在loader裏面作了特殊處理;這樣以後在每一個js裏面就可使用require或者import引入jquery
可是實際上,這個只能達到引入效果,$仍是全局對象。
我在有些前輩的帖子中看到是須要在HTML標籤中加一下引用判斷、loader標識;這樣很不友好;這裏使用了一個loader:html-withimg-loader,用這個loader,就不用管了,他本身處理HTML中出現的圖片連接。
清理已經存在的文件,若是不清理每次發佈都會有殘餘文件,雖然沒有什麼影響,可是不能忍。 CleanWebpackPlugin能夠指定清理的正則配置,如:
new CleanWebpackPlugin(["release"],{
root: path.resolve(__dirname, ".."),
verbose: true,
dry: false
}),
複製代碼
new CleanWebpackPlugin(["release/*.js","release/**/*.*"],{
root: path.resolve(__dirname, ".."),
verbose: true,
dry: false
}),
複製代碼
開發環境
'use strict';
const path = require("path");
const webpack = require("webpack");
const merge = require('webpack-merge');
const base = require('./webpack.base.conf');
// process.env.NODE_ENV = "development";
module.exports = merge(base, {
mode: "development",
devtool: "eval-source-map",
output: {
path: path.resolve(__dirname, "../release"),//"../release_dev"),
filename: "[name].[hash].js",
},
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
// loader: 'url-loader?limit=8192&name=./public/images/[name].[hash].[ext]'
loader: {
loader: 'url-loader',
options: { // 這裏的options選項參數能夠定義多大的圖片轉換爲base64
name: '[name].[hash].[ext]',
// limit: 8192, // 表示小於50kb的圖片轉爲base64,大於50kb的是路徑
// outputPath: '/public/images' //定義輸出的圖片文件夾
}
}
}
]
},
plugins:[
new webpack.HotModuleReplacementPlugin()
],
devServer: {
port: 8080,
contentBase: path.resolve(__dirname, "../release"), //本地服務器所加載的頁面所在的目錄
historyApiFallback: true, //不跳轉
inline: true, //實時刷新
hot: true, // 開啓熱更新,
//服務器代理配置項
proxy: {
'/o2o/*':{
target: 'https://www.baidu.com',
secure: true,
changeOrigin: true
}
}
}
});
複製代碼
這個在base的基礎上作了些許調整,主要是爲了使用webpack-dev-server;這個配置文件是爲它存在的。
這裏的hash有chunkhash改爲hash,緣由是使用HotModuleReplacementPlugin以後不能使用chunkhash和contenthash。
看到有些地方說把「hot:true」去掉就好了,可是我本身實際測試不行,只是去掉hot仍是會報錯;因此我索性給改爲hash了,反正是本機調試,影響不大。
這個功能很強大,對開發人員來講是很是友好的。
安裝webpack-dev-server
yarn add webpack-dev-server
複製代碼
這個代理proxy功能仍是很是強大的,將後臺服務請求指向咱們的測試環境或者本地。咱們原有的fis是包裝了一層nginx,每次還要單打開,單獨配置nginx。這裏集成這個功能,很好。本地開發減小依賴,也便於調試。
前面在entry工具中將全部的entry寫入到一個json文件中了。在這個地方就用到了,咱們項目本質上根本不是spa,使用webpack仍是比較牽強的。
當啓動了webpack-dev-server以後它會默認打開根目錄下的index.html。其實咱們項目的頁面不少,不論默認打開哪一個都不方便開發,我乾脆把這個index.html作成了一個目錄頁面。將entry.json中全部的路徑全顯示,點擊以後進入各個頁面。
// const $ = require("jquery");
import $ from "jquery";
const entryJson = require("./entry.json");
console.log(1122333,entryJson);
$(() => {
$("html").css("font-size","16px");
for (let k in entryJson){
$("body").append("<a style='margin: 1rem;padding-left:3rem;font-size: 2rem;line-height: 2rem;display: block' href='"+entryJson[k]+"'>"+entryJson[k]+"</a></br>");
}
});
複製代碼
生產環境
'use strict';
const path = require("path");
const merge = require('webpack-merge');
const base = require('./webpack.base.conf');
module.exports = merge(base, {
mode: "production",
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: "public/vendor",
chunks: "all",
minChunks: 2
}
}
}
},
module: {
rules: [
{
test:/\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.(png|jpg|gif)$/,
// loader: 'url-loader?limit=8192&name=./public/images/[name].[hash].[ext]'
loader: {
loader: 'url-loader',
options: { // 這裏的options選項參數能夠定義多大的圖片轉換爲base64
name: '[name].[hash].[ext]',
limit: 8192, // 表示小於的圖片轉爲base64,大於的是路徑
outputPath: 'public/images' //定義輸出的圖片文件夾
}
}
}
]
}
});
複製代碼
這個生產的配置也是在前面的base基礎上調整的。
這個小的工程是做爲一個子工程存在於舊項目,因此url不是直接訪問的,須要加上「工程名」的一級路徑。url-loader的outputPath、全部chunkname都須要多加一段「activity」,具體須要本身調試。
例如:
xxxx.com/index.html -> xxxx.com/activity/in… xxxx.com/public/1.cs… -> xxxx.com/activity/pu…
這個地方有個須要注意,最開始嘗試的時候,我想只要只要改output就好了;可是測試以後才發現不行。緣由很簡單,這個圖片src是給瀏覽器用的,是統一資源定位符。僅僅調整output的path是不會在定位符上加「activity」的,那僅僅是改變了發佈後文件保存的路徑。x如今須要在發佈的時候加深一個目錄級別,例如:
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: "activity/public/vendor",
chunks: "all",
minChunks: 2
}
}
}
},
複製代碼
{
test: /\.(png|jpg|gif)$/,
// loader: 'url-loader?limit=8192&name=./public/images/[name].[hash].[ext]'
loader: {
loader: 'url-loader',
options: { // 這裏的options選項參數能夠定義多大的圖片轉換爲base64
name: '[name].[hash].[ext]',
limit: 8192, // 表示小於的圖片轉爲base64,大於的是路徑
outputPath: 'activity/public/images' //定義輸出的圖片文件夾
}
}
}
複製代碼
如代碼中展現這裏使用了url-loader,而且設定limit;當圖片超過limit限制會單獨生成文件,不然就是base64存儲。
可是這裏我遇到一個棘手問題,當圖片單獨存儲時,options.name的hash值不能設置成contentHash或者chunkHash,而且也沒有找到合適的解決辦法,但願知道的朋友給我說一下。(雖然在必定程度上說不用hash值也行,可是我感受這樣很差)
使用babel轉義ECMAScript6的語法,使之兼容舊的瀏覽器。如代碼中設置loader,而後在項目根目錄建立新文件.babelrc,內容:
{
"presets": ["env"]
}
複製代碼
安裝babel
yarn add babel-core babel-loader babel-preset-env
複製代碼
這裏在webpack配置文件中設置了mode:production,而且在啓動腳本中也設置node的環境爲production。刪掉了devtool。 這裏設置的環境配合entry工具中對環境的識別,會配置壓縮設置。
以下:
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config ./webpack.config/webpack.dev.conf.js",
"pro": "cross-env NODE_ENV=production webpack --config ./webpack.config/webpack.pro.conf.js --progress",
"devServer": "webpack-dev-server --config ./webpack.config/webpack.devServer.conf.js --open --mode development",
"watch": "webpack --config ./webpack.config/webpack.dev.conf.js --watch"
}
}
複製代碼
首先安裝cross-env,用於設置node環境;在上面的腳本中能夠看到cross-env的使用
yarn add cross-env
複製代碼
上面設置兩個webpack的配置文件,可是沒有實際使用,其實使用的命令就是scripts中的內容。只不過這裏能夠是操做簡化,但咱們使用時只須要啓動腳本,以下: 開發環境:
yarn run devServer
複製代碼
生產環境:
yarn run pro
複製代碼
run也是能夠省略的。 webpack-dev-server模式下不會將實際發佈的內容寫入在硬盤上,若是咱們須要自行查看內容,能夠執行:
yarn run watch
複製代碼
只不過這樣作意義不大,由於我發現,你每次修改都會產生一些列文件,很快你就發現生成的是一堆垃圾,從中找東西費勁的很。
- 大圖片大單分割出來後沒法使用contenthash,我如何能讓一個大圖長久緩存吶
- 公共文件過大,僅我寫的這個測試工程vender就已經一兆多,感受不是很大,可是真實項目中就很可怕了。並且咱們項目是移動端的,這樣大文件下載的留白時間也很難受。