摘要:的架構設計促使第三方開發者讓核心發揮出無限的潛力。當然建置比起開發是較進階的議題,因為我們必須要理解內部的一些事件。這個編譯結果包含的訊息包含模組的狀態,編譯後的資源檔,發生異動的檔案,被觀察的相依套件等。
本文將對 webpack 周邊的 middleware 與 plugin 套件等作些介紹,若您對於 webpack 還不了解可以參考這篇彙整的翻譯。
webpack dev server 是什麼?
webpack dev server 是一個開發伺服器,內建 webpack 使用的 live reloading 功能。
那 webpack dev middleware 是啥?
它就是一個用來組織包裝 webpack 使其可以變成中介軟體,或稱中間件的容器。回想一下 express 你大概可以明白關於 middleware 的用途,就是在輸入到輸出的過程中 加工 的一種手段。單純說 middleware 的話我們可以想成一系列任務, 動作 (actions stack),不只 express 有,在 Ruby 中的 rake 也具備這種機制。
先看看web dev server的說明
The webpack-dev-server is a little node.js Express server, which uses the webpack-dev-middleware to serve a webpack bundle.
從頭說起的話就是 webpack 本身只負責打包編譯的功能 bundle , webpack-dev-server 當然就是協助我們開發的伺服器,這個伺服器底層是靠 express 來實作的,接著思考一下我們要如何更新(live reload)呢? 當然是需要取得 webpack 編好的資料啊,於是就需要在從 request 到 response 的過程中透過 express 的 middleware 取得資料,而方法就是透過 webpack-dev-middleware 。
比起直接編譯成檔案,webpack-dev-middleware 這個套件還多了一些好處:
不需要一直寫入磁碟,所有產生的結果會直接存在記憶體
在監視模式(watch mode)下如果檔案發生異動,middleware 會馬上停止提供舊版的 bundle 並且會延遲請求的回應直到編譯完成,如此一來我們就不需要去觀察編譯是否結束了
webpack hot middleware 是什麼?
我們都知道 webpack dev server 有提供一種Hot Module Replacement/Hot Reloading 熱替換的功能。在一般 webpack-dev-server 的時候我們會在 webpack.config.js 加入 new webpack.HotModuleReplacementPlugin() 或設定來啟用。
而 webpack hot middleware 是給 webpack-dev-middleware 用的。就是讓我們在一般的 server 上加上熱替換的功能,總結來說就是 webpack-dev-middleware + webpack-hot-middleware 即可讓我們用 express 客製一個有熱替換功能的 webpack 開發伺服器。
使用 webpack-dev-server 當中介軟體
webpack 提供了 express 的 middleware 讓我們可以處理一些靜態資源檔而不是使用 express.static 。要達成這項功能,我們需要安裝 webpack-dev-middleware 和 webpack-hot-middleware
$ npm i webpack express webpack-dev-middleware webpack-hot-middleware -D
安裝完成套件之後,首先我們需要設定一個 webpack.dev.config.js 檔案,並且在 entry 中加上 webpack/hot/dev-server 和 webpack-hot-middleware/client
entry: [
"webpack/hot/dev-server",
"webpack-hot-middleware/client",
"client/index.js"
]
這個 webpack.config 主要是給開發伺服器用的,由於這時的匯出都會存在記憶體中,因此 path 可以直接設為根
output: {
path: "/",
publicPath: "http://localhost:8080/scripts/",
filename: "bundle.js"
}
最後補上任何您所需要的 loaders,最重要的是記得。
plugins: [
new webpack.HotModuleReplacementPlugin()
]
接著下來我們開始來撰寫這個開發環境的設定檔和 express 程式。
我們會匯入 webpack,webpack-dev-middleware, webpack-hot-middleware 和 express。
若需要搭配樣板引擎請自行安裝 ejs 或 jade
var express = require("express")
var webpack = require("webpack")
var WebpackDevMiddleware = require("webpack-dev-middleware")
var WebpackHotMiddleware = require("webpack-hot-middleware")
載入套件之後,使用 express 建立一個 http 應用程式與路由
app = express()
router = express.Router()
router.get("/", MainController)
app.use(router)
上面只是一個一般的 Server 應用,為了達成 webpack 的神奇黑魔法我們需要匯入 webpack 的設定
var config = require("./webpack.dev.config")
webpack 的角色就是我們的編譯器,透過下面的程式碼建立編譯器的 instance
var compiler = webpack(config)
重點來了,我們有了伺服器 express,有了編譯核心 webpack,接著我們需要 wrapper 來打包 webpack 將其合進 express 的 middleware stack 中。
app.use(WebpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
stats: { colors: true }
}))
publicPath 就是我們想要存取前端 bundle 的網址,路徑,位置。然後我們要再加上 webpack-hot-middleware 使其具備熱替換的功能。
app.use(WebpackHotMiddleware(compiler, {
log: console.log
}))
最後則是 express 的監聽事件
app.listen(8080, function () {
console.log("Listening on 8080")
})
完整的 server 程式碼如下
var express = require("express")
var webpack = require("webpack")
var WebpackDevMiddleware = require("webpack-dev-middleware")
var WebpackHotMiddleware = require("webpack-hot-middleware")
var config = require("./config/webpack.dev.config")
var compiler = webpack(config)
app = express()
app.set("views", "./views")
app.set("view engine", "ejs")
app.use(express.static("public"));
app.use(WebpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
stats: { colors: true }
}))
app.use(WebpackHotMiddleware(compiler))
var router = express.Router()
router.get("/", function (req, res, next) {
res.render("index", { message: "Hey there!"});
})
app.use(router)
app.listen(8080, function () {
console.log("Listening on 8080")
})
換個思路
假設我們並不是要實作一個全站 SPA 的站,實務上我們的確會遇到需要拆分為許多 view .html 的狀況,這種情況下我們會希望自己客製的這個 server 就像 webpack-dev-server 一樣,當然,這邊只是要指出做法,如果一樣您當然就直接用 webpack-dev-server 就好了。
根據上面這個需求最簡單的方式就是透過 express.static(__dirname) 讓 express 直接 return raw 檔案。
html-webpack-plugin
小弟認為在學習的過程中,最重要的就是搞懂動機,而這個 html-webpack-plugin 插件,其用途就是簡化建立 html 的過程。
先回頭看看上一小節,很直覺的,我們會依據需求建立不同的頁面(.html) ,因為在開發過程中很多時候前端只需要注重那些互動介面的邏輯 ,樣式 ,樣板 ,標籤結構 ,那我們的重點只有 client 端的 html, js, css 就不在話下了吧!再如果我們又以元件 為思路中心來設計實踐的話,那麼 html 裡面大部分的東西都會往元件的 template 搬。依據 SPA 的思路,html 的責任就只是把我們的 bundle 載入並掛載 root component 。
如果照著這樣的想法,不斷的新增 html 結果大部分的內容都是重複的那就不太靠譜啦。我們就需要一種簡化工作的方式。
這個套件如上面所說就是簡化建立載入 bundle 的 html 的步驟,用在 webpack 打包的檔案包含每次編譯都會更新的 hash 時特方便。
我們可以讓套件幫我們產生 html 或者搭配 loaders 與其他樣版引擎。
基本的用法
第一種最簡單的用途就是為我們的 bundle 包上一層 html
plugins: [
new HtmlWebpackPlugin({
filename: "i_love_this_file.html"
})
]
如果我們有多個 entry 進入點,那麼所有的 bundle 都會被加進這個自動產生的 HTML 中。 如果我們透過 webpack 匯出了 css 資源檔(例如 extract-text-plugin) 那麼這些檔案也會透過 被加入 HTML 中。
html-webpack-plugin 的設定
當然這個套件也有一些參數,讓我們可以透過設定提供其他的功能。
title : 設定該 html 的 </b> 標籤</p></p>
<p><p><b>filename</b>: html 檔名,也當作路徑存取。預設是 <b>index.html</b></p></p>
<p><p><b>template</b>: 樣板的路徑,也就是說我們可以先組織 HTML 在載入讓 <b>html-webpack-plugin</b> 幫我們注入(inject) bundle。此部分要注意相對路徑是從 server 程式檔案出發。</p></p>
<p>
<p><b>inject</b>: 將所有的資源檔注入 <b>template</b> 或 <b>templateContent</b>,當值是 <b>true</b>, <b>"body"</b> 的時候所有的 js 資源檔都會被注入 <b></body></b> 之前,<b>"head"</b> 則是 <b><head></b> 之間,<b>false</b> 自然就是關閉</p>
<p><p>true: Boolean</p></p>
<p><p>false: Boolean</p></p>
<p><p>head: String</p></p>
<p><p>body: String</p></p>
</p>
<p><p><b>favicon</b>: 替 HTML 加上 favicon 路徑</p></p>
<p>
<p><b>minify</b>: 傳入 html-minifier 參數物件,壓縮輸出。</p>
<p><p>options: Object</p></p>
<p><p>false: Boolean</p></p>
</p>
<p>
<p><b>hash</b>: <b>true</b> 時替 webpack 編譯的檔案或結果路徑結尾補上 hash,這麼做的用意是在開發時期當檔案有異動時可以避免瀏覽器快取</p>
<p><p>true: Boolean</p></p>
<p><p>false: Boolean</p></p>
</p>
<p>
<p><b>cache</b>: 預設是 <b>true</b> 快取檔案,除非檔案有異動</p>
<p><p>true: Boolean</p></p>
<p><p>false: Boolean</p></p>
</p>
<p>
<p><b>showErrors</b>: 預設 <b>true</b> 例外或錯誤資訊會寫入 html 頁面</p>
<p><p>true: Boolean</p></p>
<p><p>false: Boolean</p></p>
</p>
<p><p><b>chunks</b>: 允許我們加入一些程式碼片段,例如單元測試</p></p>
<p>
<p><b>chunksSortMode</b>: 控制 chunks 排序</p>
<p><p>none: String</p></p>
<p><p>auto: String</p></p>
<p><p>dependency: String</p></p>
<p><p>{}: Function</p></p>
</p>
<p><p><b>excludeChunks</b>: 略過部分 chunk 程式碼片段</p></p>
<p>
<p><b>xhtml</b>: 設定為 <b>true</b> 的話 <b>link</b> 標籤會是 self-closing ,預設是 <b>false</b></p>
<p><p>true: Boolean</p></p>
<p><p>false: Boolean</p></p>
</p>
<b>腦力激盪 - 如果要多個頁面搭配各自的 bundle?</b>
<p>webapck 難就難在其靈活之中伴隨著複雜,不同的思路有著不同的做法。這一小節目的是為了不讓我們對 webpack 使用上僵化而提出的一個小題目。</p>
<p>要達成這個需求,我們可以先使用 webpack.config 中 <b>[name]</b> 的功能拆分我們的 bundle</p>
<pre>{
entry: {
a: "./path/src/a",
b: "./path/src/b",
c: "./path/src/c"
},
output: {
filename: "[name].bundle.js"
}
}</pre>
<p>接著透過 <b>html-webpack-plugin</b> 的參數,把 <b>inject: false</b> 然後 <b>template</b> 在各自的 template 中使用 bundle。</p>
<b>
html-webpack-template - 更牛的方式</b>
<p>照著上面的方式你可能又跟我抱怨,那不是又要產一堆 HTML 了嗎? 對啊!原本這個架構就是針對 SPA 設計的嘛。不過透過這樣來來回回的思考動機與流程我相信對於您日後使用 webpack 與閱讀設定有很大的幫助。現在的問題是 - 你覺得產一大堆 HTML 不是很靠譜,於是我們就有了 <b>html-webpack-template</b> 的產生啦。</p>
<p>這個東西大略的用法就是</p>
<pre>plugins: [
new HtmlWebpackPlugin({
title: "Sample",
filename: "sample.html"
}),
new HtmlWebpackPlugin({
inject: false, // 必須
template: require("html-webpack-template"), // 必須
filename: "sp.html", // 存取的路徑
// 只需要特定 bundle 可以這樣設定
chunks: ["vender"],
title: "OH My Gosh",
// 可以參考 html-webpack-template 的參數設定
// 下面為提供 GA
googleAnalytics: {
trackingId: "UA-XXXX-XX",
pageViewOnLoad: true
}
})
]</pre>
<b>html-webpack-plugin 事件</b>
<p>特地介紹此套件的事件也是因為挺有可能會需要一些時間點對 html 動些手腳,有了事件的機制我們就可以讓<b>其他套件</b>修改產生的 html</p>
<p>非同步事件:</p>
<p><p><b>html-webpack-plugin-before-html-generation</b></p></p>
<p><p><b>html-webpack-plugin-before-html-processing</b></p></p>
<p><p><b>html-webpack-plugin-after-html-processing</b></p></p>
<p><p><b>html-webpack-plugin-after-emit</b></p></p>
<p>同步事件:</p>
<p><p><b>html-webpack-plugin-alter-chunks</b></p></p>
<p>大略的用法就是在透過 hook event 綁定的事件做些處理</p>
<pre>compiler.plugin("compilation", function(compilation) {
console.log("The compiler is starting a new compilation...");
compilation.plugin("html-webpack-plugin-before-html-processing", function(htmlPluginData, callback) {
htmlPluginData.html += "The magic footer";
callback(null, htmlPluginData);
});
});</pre>
<b>webpack-hot-middleware</b>
<p><b>webpack-hot-middleware</b> 這個套件只能搭配 <b>webpack-dev-middleware</b> 使用,其實就是把熱替換的功能加到一般 server 應用。</p>
<p>這個模組只專注在處理 webpack 和瀏覽器溝通的機制。這個中介軟體會去訂閱監聽開發伺服器,當更新或異動發生的時候它就透過 webpack 的 HMR API 來更新。實際上讓您的程式能無縫的使用熱替換已超過本文範圍,在這部分通常會靠其他模組來處理。</p>
<p>安裝完套件與在伺服器 app 中套用之外,要記得 webpack.config 的 plugin 也要加上 <b>HotModuleReplacementPlugin</b></p>
<pre>plugins: [
// Webpack 1.0
new webpack.optimize.OccurenceOrderPlugin(),
// Webpack 2.0 fixed this mispelling
// new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
]</pre>
<p>簡短地介紹一下 <b>OccurrenceOrderPlugin</b> 部份,您應該知道 webapck 會給編譯好的程式碼片段一個 id 以用來辨別。</p>
<p>透過上面的這個 plugin 可以讓 webpack 在 id 的分派上優化並保持一致性。</p>
<p>接著要在 entry point 加上 <b>webpack-hot-middleware/client</b> 這隻檔案會連到 server 目的是當 server 重新編譯好檔案時收到通知然後更新 client 的檔案。</p>
<b>如何撰寫 plugin</b>
<p>為什麼要了解怎麼寫 plugin 呢? 因為某些 plugin 可以擴展支援其他 plugin 相互傳遞資料或需要客製後續任務,所以稍微明白 plugin 的寫法可以讓我們對於 plugin 的設定更加清楚。</p>
<p>plugin 的架構設計促使第三方開發者讓 webpack 核心發揮出無限的潛力。在不同建置階段執行 callback ,開發者可以自訂出特有的行為。</p>
<p>當然建置 plugin 比起開發 loader 是較進階的議題,因為我們必須要理解 webpack 內部的一些 hook 事件。</p>
<h5>編譯器與編譯結果</h5>
<p>要開發 plugin 第一步就是先了解其中最重要的兩個角色 <b>compiler</b> 和 <b>compilation</b> 物件</p>
<p><p><b>compiler</b> 編譯器物件代表一個完整設定的 webpack 環境。這個物件在 webpack 發動之後就會被建置,而且只會建置一次。然後它會配置所有可以操作的設定包含 <b>loaders</b>, <b>plugins</b>。當我們套用一個 plugin 這個 plugin 會收到 <b>compiler</b> 的參考透過存取這個<b>參考 reference</b>就可以取得 webpack 環境</p></p>
<p><p><b>compilation</b> 編譯成果這個物件代表的是<b>某個版本的編譯後的資源檔</b>,在運行 webpack dev middleware 期間每當檔案發生異動就會產生一個新的 <b>compilation</b> 也就是產生新的編譯結果。這個<b>編譯結果</b>包含的訊息包含 module 模組的狀態,編譯後的資源檔,發生異動的檔案,被觀察的相依套件等。這個編譯結果物件也提供一些執行 callback 的機會讓我們可以在過程中客製一些自己想要的行為。</p></p>
<p>任何 webpack plugin 都必須依靠這兩者來完成,所以有需要對其原始碼有些大概的了解</p>
<p><p>Compiler Source</p></p>
<p><p>Compilation Source</p></p>
<b>基本 plugin 架構</b>
<p>本質上來說 plugin 只是一個物件實例具有 apply 方法,這個 <b>apply</b> 會在安裝時期被 <b>webpack compiler</b> 執行一次。</p>
<p>透過這一次的執行呢我們就可以繫結許多事件,直接來看看程式碼您就明白了。</p>
<pre>function MyPlugin(options) {
// 設定參數
this.options = options
}
MyPlugin.prototype.apply = function(compiler) {
compiler.plugin("done", function() {
// 當 plugin 安裝完成就會...
console.log("Hello World!");
})
// 我們自然需要拿到編譯的結果
compiler.plugin("compilation", function(compilation) {
console.log(compilation.assets)
compilation.plugin("optimize", function() {
console.log("Assets are being optimized.");
});
});
}</pre>
<p>OK! 我們現在並不是要開發套件所以點到這邊我想就足夠了,剩下的您可以自行參考相關文件。</p>
<p><p>詳細 plugin API</p></p>
<b>extract-text-webpack-plugin</b>
<p>顧名思義這個 plugin 的用途就是把 text 類型的結果匯出成一個檔案,先說這不是非常精確的描述,但概念來說 text 類型指的就是<b>不會</b>輸出成 <b>module.exports</b> 或 <b>json</b> 的資料。而像是 CSS 這類的資源檔 webpack 其實最終就是在 JS 中幫我們建個 style tag 的 dom 然後整包放進去。<b>file-loader</b>, <b>raw-loader</b> 等等這類內容大略就屬於 text 類型。查閱各種 loaders 回傳資料類型</p>
<p>於是乎以 entry point 為單位過程中解析的 text 內容就會被抽出來匯出成一個檔案。最常見的用法就是把 css 抽出來:</p>
<pre>var ExtractTextPlugin = require("extract-text-webpack-plugin")
module.exports = {
module: {
loaders: [
{ test: /.css$/, loader: ExtractTextPlugin.extract("style", "css") }
]
},
plugins: [
// 注意: 這邊的副檔名如果亂下是會造成瀏覽器行為不符合預期的,例如不給副檔名那瀏覽器就會當作 binary 下載
new ExtractTextPlugin("styles.css")
]
}</pre>
<p>如果想要拆分多個檔案,那麼就先初始化 instance</p>
<pre>let ExtractTextPlugin = require("extract-text-webpack-plugin");
// multiple extract instances
let cssExtractor = new ExtractTextPlugin("stylesheets/[name].css");
let lessExtractor = new ExtractTextPlugin("stylesheets/[name].less");
module.exports = {
module: {
loaders: [
{test: /.scss$/i, loader: cssExtractor.extract(["css","sass"])},
{test: /.less$/i, loader: lessExtractor.extract(["css","less"])},
...
]
},
plugins: [
cssExtractor,
lessExtractor
]
}</pre>
<b>HMR 熱替換</b>
<p>Hot Module Replacement (HRM) 又稱熱替換,功能就是在程式運行中交換,移除,增加模組且不會使頁面重新載入。這跟我們伺服器的熱插拔差不多概念。</p>
<h5>它是怎麼運作的?</h5>
<p>webpack 在 bundle 中即我們的 js 裡加入了一個小型的 HMR 執行環境,在編譯過程中這個 runtime 會在我們的 app 中運行。</p>
<p>當建置完成時 webpack 也不會消失反而會持續存在,繼續監控原始碼檔案是否發生修改。一旦 webpack 發現程式有改變他就會去重新編譯那些有修改的模組,不全部重建。根據設定要嘛就是 webpack 把訊號丟給 HRM runtime 要嘛就是 HRM 自己更新異動資訊。不管哪種方式反正重點就是 HRM runtime 會取得修改的模組,接著就試著在運行的狀態下更新模組。首先會先檢查更新的模組是否能 <b>self-accept</b>。</p>
<p>關於 <b>self-accept</b> 先看看範例和原始碼,意思是要<b>支援熱替換的模組或說編譯結果</b>基本上是應該要實作 <b>module.hot.accept</b> 和遵循其他熱替換的規則。</p>
<p>如果沒有辦法自己確認自己可以直接被更新,那就往上傳,通知那些 require 匯入使用自己的模組更新,就這樣層層往上。直到有人可以 accept 或到頂,不過一旦到根就表示熱替換失敗。</p>
<p>讀到這邊你可能通了,為什麼當我們要讓 React 支援 Hot Mode 的時候需要一個 <b>react-hot-loader</b>。以及因為要和 HRM 執行環境溝通的關係我們需要在 bundle 的 entry point 加上 <b>webpack/hot/dev-server</b>, <b>webpack-hot-middleware/client</b> 之類的東西。</p>
<h5>從 App 的角度</h5>
<p><script type="text/javascript">showImg("https://segmentfault.com/img/remote/1460000006763873");</script></p>
<p>當 App 程式開始執行(就是載入 bundle) HMR runtime 執行環境就會啟用,接下來程式就會要求 HMR runtime 幫我們檢查是否需要更新。HMR 會幫我們下載更新然後通知 App 程式有哪些更新可用。</p>
<h5>從編譯器(webpack/compiler)的角度</h5>
<p>除了一般的資源檔像是圖片,css,編譯器還需要觸發<b>更新事件</b>讓程式碼可以完成新舊替換。這個"更新"包含兩個部分</p>
<p><p>更新的 Manifest 支援配置文件(json)</p></p>
<p><p>一或多個更新的<b>chunks</b>程式片段(js)</p></p>
<p>支援配置文件包含更新後編譯結果的 hash 和新的 chunks 程式碼片段的列表。而新的 chunks 則包含更新後模組的程式碼或 <b>flag</b>。</p>
<p>編譯器同時也會確保模組和片段 ID 是一致的,透過一個 <b>records</b> 的 json 檔案來儲存相關資訊。</p>
<h5>從模組角度</h5>
<p>HMR 是選擇性的功能,所以只有在模組包含 HRM 程式碼才會被影響作用。也就是在模組中使用文件有提供的 API。一般來說模組的開發者 handler 會在模組相依的部分更新時被執行。當然也可以寫一個 handler 在這個模組更新時被呼叫。</p>
<p>在大部分的情況並不需要為每一個模組都撰寫<b>支援 HMR 的程式碼</b>,當一個模組沒有遵循處理規則時就會往上層傳遞事件,意味著只有上方有一個 handler 可以處理就好,但不要讓這個冒泡事件一路冒到頂喔。</p>
<h5>從 HMR runtime 角度</h5>
<p>模組系統的執行環境其實是額外加入的程式,用來追蹤模組之間的父子關係。</p>
<pre>if(module.hot) {
...
}</pre>
<p>從管理的角度,這個執行環境 runtime 支援 <b>check</b> 和 <b>apply</b> 兩個方法。</p>
<p><b>check</b> 的功能是發出 HTTP request 用來取得上面提到的 Manifest,當 request 失敗時就等於沒有任何更新。否則就會依照得到的<b>更新列表</b>去比對 chunks。</p>
<p>對每個已載入的 chunk 都會有對應更新的程式碼要被下載。所有模組更新會被存在 runtime 中準備拿來更新。當執行環境切換成 <b>ready</b> 狀態就表示更新的程式碼都被下載完成了隨時可以套用。</p>
<p>接著 <b>apply</b> 方法會將所有已更新的模組的 <b>flag</b> 標記為 <b>invalid</b> 無效,然後無效的模組需要 update 的 handler 處理函式,這個 handler 會在模組中或者父節點上。只要沒有這個 handler 就會持續往上曾傳遞並標註為 <b>invalid</b>,一旦冒泡機制冒到頂端即 <b>entry point</b> 就表示熱替換失敗。</p>
<p>所有被標記為無效的模組都會透過 <b>module.hot.dispose</b> 卸載,然後更新 hash,再來所有 <b>module.hot.accept</b> 的 handlers 會被調用。</p>
<p>執行環境切回 <b>idle</b> 狀態表示所有更新都完成了。</p>
<p>講這麼多其實簡單來說就是我們的模組要補一些 hot mode 的邏輯</p>
<pre>var app = require("./app");
// 模擬每 5 秒更新一次
setInterval(function() {
console.log(app(new Date()));
}, 5000);
if(module.hot) {
module.hot.accept("./app", function() {
app = require("./app");
});
}</pre>
<h5>檔案的更新流程</h5>
<p>左邊表示初始化時編譯器產生的結構,右邊則是當模組 4 和 9 更新時的流程。<br>方塊表示從 Entry 開始,webpack 幫我們編譯產生的部份從 Entry 然後轉換成 Chunk 0 - 4</p>
<p><script type="text/javascript">showImg("https://segmentfault.com/img/remote/1460000004840685");</script></p>
<b>資源參考</b>
<p><p>html-webpack-plugin</p></p>
<p><p>webpack dev middleware 說明</p></p>
<b>備註</b>
<p>部分內容可能理解不夠精確若有錯誤歡迎指教留言</p>
</div>
<div id="usa00a2" class="mt-64 tags-seach" >
<div id="ssqow2w" class="tags-info">
<a style="width:120px;" title="云服務器" href="http://m.specialneedsforspecialkids.com/site/active/kuaijiesale.html?ytag=seo">云服務器</a>
<a style="width:120px;" title="GPU云服務器" href="http://m.specialneedsforspecialkids.com/site/product/gpu.html">GPU云服務器</a>
<a style="width:120px;" title="與相關" href="http://m.specialneedsforspecialkids.com/yun/tag/??xiang??/">與相關</a>
<a style="width:120px;" title="webpack-dev-server" href="http://m.specialneedsforspecialkids.com/yun/tag/webpack-dev-server/">webpack-dev-server</a>
<a style="width:120px;" title="深入淺出webpack" href="http://m.specialneedsforspecialkids.com/yun/tag/shenruqianchuwebpack/">深入淺出webpack</a>
<a style="width:120px;" title="深入理解系列" href="http://m.specialneedsforspecialkids.com/yun/tag/shenrulijiexilie/">深入理解系列</a>
</div>
</div>
<div id="omius0a" class="entry-copyright mb-30">
<p class="mb-15"> 文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。</p>
<p>轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/79623.html</p>
</div>
<ul class="pre-next-page">
<li id="2eeq02k" class="ellipsis"><a class="hpf" href="http://m.specialneedsforspecialkids.com/yun/79622.html">上一篇:React開發 獲取不到當前觸發事件的元素</a></li>
<li id="mwoq2kw" class="ellipsis"><a class="hpf" href="http://m.specialneedsforspecialkids.com/yun/79624.html">下一篇:zepto.cutphoto 頭像裁剪小工具</a></li>
</ul>
</div>
<div id="2akssio" class="about_topicone-mid">
<h3 class="top-com-title mb-0"><span data-id="0">相關文章</span></h3>
<ul class="com_white-left-mid atricle-list-box">
<li>
<div id="40omkqy" class="atricle-list-right">
<h2 class="ellipsis2"><a class="hpf" href="http://m.specialneedsforspecialkids.com/yun/107860.html"><b>【敲黑板】<em>手<em>把手</em></em>教你vue-cli單頁到多頁應用</b></a></h2>
<p class="ellipsis2 good">摘要:到多頁應用前言我有一個創建的項目,但是我想做成多頁應用,怎么辦,廢話不多說,直接開擼約定新增代碼部分在和中間刪除注釋代碼部分在和中間,很多東西都寫在注釋里第一步一個項目新建一個項目官網默認使用的服務,這個服務是做不了單頁的,需要手動建一
vue-cli到多頁應用
前言:我有一個cli創建的vue項目,但是我想做成多頁應用,怎么辦,廢話不多說,直接開擼~
約定:新增代碼部分在//add和...</p>
<div id="sekg02y" class="com_white-left-info">
<div id="4ugqema" class="com_white-left-infol">
<a href="http://m.specialneedsforspecialkids.com/yun/u-215.html"><img src="http://m.specialneedsforspecialkids.com/yun/data/avatar/000/00/02/small_000000215.jpg" alt=""><span id="yqi2m2k" class="layui-hide64">DC_er</span></a>
<time datetime="">2019-08-26 11:56</time>
<span><i class="fa fa-commenting"></i>評論0</span>
<span><i class="fa fa-star"></i>收藏0</span>
</div>
</div>
</div>
</li>
<li>
<div id="o2e0wme" class="atricle-list-right">
<h2 class="ellipsis2"><a class="hpf" href="http://m.specialneedsforspecialkids.com/yun/90923.html"><b>前端臨床手札——<em>webpack</em>構建逐步解構(上)</b></a></h2>
<p class="ellipsis2 good">摘要:前言由于博主最近又閑下來了,之前覺得的官方文檔比較難啃一直放到現在。文章會逐步分析每個處理的用意當然是博主自己的理解,不足之處歡迎指出溝通交流。后續將會補上構建生產的配置分析,案例參考。前端臨床手札構建逐步解構下
前言
由于博主最近又閑下來了,之前覺得webpack的官方文檔比較難啃一直放到現在。細心閱讀多個webpack配置案例后覺得還是得自己寫個手腳架,當然這個案例是基于vue的,...</p>
<div id="ikua0q0" class="com_white-left-info">
<div id="cgaea0m" class="com_white-left-infol">
<a href="http://m.specialneedsforspecialkids.com/yun/u-1459.html"><img src="http://m.specialneedsforspecialkids.com/yun/data/avatar/000/00/14/small_000001459.jpg" alt=""><span id="gi2kswy" class="layui-hide64">lowett</span></a>
<time datetime="">2019-08-22 10:36</time>
<span><i class="fa fa-commenting"></i>評論0</span>
<span><i class="fa fa-star"></i>收藏0</span>
</div>
</div>
</div>
</li>
<li>
<div id="0eagqi0" class="atricle-list-right">
<h2 class="ellipsis2"><a class="hpf" href="http://m.specialneedsforspecialkids.com/yun/99190.html"><b><em>webpack</em>4配置詳解之逐行分析</b></a></h2>
<p class="ellipsis2 good">摘要:今天就嘗試著一起來聊聊吧,旨在幫大家加深理解新手更容易上路,都能從到搭建配置自定屬于自己的腳手架,或對已封裝好的腳手架有進一步的鞏固,接下來蘇南會詳細講解中的每一個配置字段的作用部分為新增。
showImg(https://segmentfault.com/img/bVbjmMV?w=1008&h=298);
前言
經常會有群友問起webpack、react、redux、甚至cre...</p>
<div id="wmiag0w" class="com_white-left-info">
<div id="ko2sy2s" class="com_white-left-infol">
<a href="http://m.specialneedsforspecialkids.com/yun/u-1481.html"><img src="http://m.specialneedsforspecialkids.com/yun/data/avatar/000/00/14/small_000001481.jpg" alt=""><span id="kqksyqm" class="layui-hide64">dkzwm</span></a>
<time datetime="">2019-08-23 13:04</time>
<span><i class="fa fa-commenting"></i>評論0</span>
<span><i class="fa fa-star"></i>收藏0</span>
</div>
</div>
</div>
</li>
<li>
<div id="ggemias" class="atricle-list-right">
<h2 class="ellipsis2"><a class="hpf" href="http://m.specialneedsforspecialkids.com/yun/102835.html"><b><em>webpack</em>-<em>dev</em>-<em>middleware</em>@1.12.2 源碼解讀</b></a></h2>
<p class="ellipsis2 good">摘要:如果此時我們不想把文件輸出到內存里,可以通過修改的源代碼來實現。服務啟動成功。。。根據請求的,拼接出
? webpack-dev-middleware 是express的一個中間件,它的主要作用是以監聽模式啟動webpack,將webpack編譯后的文件輸出到內存里,然后將內存的文件輸出到epxress服務器上;下面通過一張圖片來看一下它的工作原理:
showImg(https:...</p>
<div id="yq022es" class="com_white-left-info">
<div id="wgam2ou" class="com_white-left-infol">
<a href="http://m.specialneedsforspecialkids.com/yun/u-1200.html"><img src="http://m.specialneedsforspecialkids.com/yun/data/avatar/000/00/12/small_000001200.jpg" alt=""><span id="mqa2smq" class="layui-hide64">yearsj</span></a>
<time datetime="">2019-08-23 16:15</time>
<span><i class="fa fa-commenting"></i>評論0</span>
<span><i class="fa fa-star"></i>收藏0</span>
</div>
</div>
</div>
</li>
<li>
<div id="as0ksms" class="atricle-list-right">
<h2 class="ellipsis2"><a class="hpf" href="http://m.specialneedsforspecialkids.com/yun/96221.html"><b><em>webpack</em>使用記錄</b></a></h2>
<p class="ellipsis2 good">容易混淆概念解析
讀這篇文章理清下面概念 webpack 中那些最易混淆的 5 個知識點
1.module,chunk 和 bundle 的區別是什么?2.filename 和 chunkFilename 的區別
版本區別
webpack 2x
entry
output
loaders
file-loader:把文件輸出到一個文件夾中,在代碼中通過相對 URL 去引用輸出的文件
url-lo...</p>
<div id="2gcemos" class="com_white-left-info">
<div id="uicwesw" class="com_white-left-infol">
<a href="http://m.specialneedsforspecialkids.com/yun/u-730.html"><img src="http://m.specialneedsforspecialkids.com/yun/data/avatar/000/00/07/small_000000730.jpg" alt=""><span id="q2sy20s" class="layui-hide64">Sike</span></a>
<time datetime="">2019-08-22 18:48</time>
<span><i class="fa fa-commenting"></i>評論0</span>
<span><i class="fa fa-star"></i>收藏0</span>
</div>
</div>
</div>
</li>
</ul>
</div>
<div id="suemkss" class="topicone-box-wangeditor">
<h3 class="top-com-title mb-64"><span>發表評論</span></h3>
<div id="siek20k" class="xcp-publish-main flex_box_zd">
<div id="kcwsoce" class="unlogin-pinglun-box">
<a href="javascript:login()" class="grad">登陸后可評論</a>
</div> </div>
</div>
<div id="guo0umc" class="site-box-content">
<div id="yckk20o" class="site-content-title">
<h3 class="top-com-title mb-64"><span>0條評論</span></h3>
</div>
<div id="wwskwqu" class="pages"></ul></div>
</div>
</div>
<div id="gwemwau" class="layui-col-md4 layui-col-lg3 com_white-right site-wrap-right">
<div id="kagowe0" class="">
<div id="wk2q20m" class="com_layuiright-box user-msgbox">
<a href="http://m.specialneedsforspecialkids.com/yun/u-380.html"><img src="http://m.specialneedsforspecialkids.com/yun/data/avatar/000/00/03/small_000000380.jpg" alt=""></a>
<h3><a href="http://m.specialneedsforspecialkids.com/yun/u-380.html" rel="nofollow">gitmilk</a></h3>
<h6>男<span>|</span>高級講師</h6>
<div id="e02ygk0" class="flex_box_zd user-msgbox-atten">
<a href="javascript:attentto_user(380)" id="attenttouser_380" class="grad follow-btn notfollow attention">我要關注</a>
<a href="javascript:login()" title="發私信" >我要私信</a>
</div>
<div id="cqw0ss2" class="user-msgbox-list flex_box_zd">
<h3 class="hpf">TA的文章</h3>
<a href="http://m.specialneedsforspecialkids.com/yun/ut-380.html" class="box_hxjz">閱讀更多</a>
</div>
<ul class="user-msgbox-ul">
<li><h3 class="ellipsis"><a href="http://m.specialneedsforspecialkids.com/yun/121948.html">基于javaweb+jsp的醫院信息管理系統</a></h3>
<p>閱讀 2032<span>·</span>2021-10-09 09:41</p></li>
<li><h3 class="ellipsis"><a href="http://m.specialneedsforspecialkids.com/yun/121406.html">大齡業余程序員要搞個django小程序--09--小程序登陸狀態維護之個人中心如何做數據加載</a></h3>
<p>閱讀 1606<span>·</span>2021-09-28 09:36</p></li>
<li><h3 class="ellipsis"><a href="http://m.specialneedsforspecialkids.com/yun/121126.html">程序員的算法趣題Q39: 反復排序</a></h3>
<p>閱讀 1110<span>·</span>2021-09-26 09:55</p></li>
<li><h3 class="ellipsis"><a href="http://m.specialneedsforspecialkids.com/yun/119841.html">DediPath:美國特價獨立服務器,洛杉磯獨服月付$39起,全場VPS一律4折優惠</a></h3>
<p>閱讀 1300<span>·</span>2021-09-10 11:17</p></li>
<li><h3 class="ellipsis"><a href="http://m.specialneedsforspecialkids.com/yun/118753.html">GigsGigsCloud:洛杉磯CN2+聯通9929線路VPS限時2折</a></h3>
<p>閱讀 1155<span>·</span>2021-09-02 09:56</p></li>
<li><h3 class="ellipsis"><a href="http://m.specialneedsforspecialkids.com/yun/115209.html">CSS3魔法堂:說說Multi-column Layout</a></h3>
<p>閱讀 2769<span>·</span>2019-08-30 12:58</p></li>
<li><h3 class="ellipsis"><a href="http://m.specialneedsforspecialkids.com/yun/112339.html">js實現鼠標拖拽多選功能</a></h3>
<p>閱讀 2938<span>·</span>2019-08-29 13:03</p></li>
<li><h3 class="ellipsis"><a href="http://m.specialneedsforspecialkids.com/yun/109278.html">摸倚天魚文章推薦系列 - 19/03/31</a></h3>
<p>閱讀 1865<span>·</span>2019-08-26 13:40</p></li>
</ul>
</div>
<!-- 文章詳情右側廣告-->
<div id="kwum0es" class="com_layuiright-box">
<h6 class="top-com-title"><span>最新活動</span></h6>
<div id="syskew2" class="com_adbox">
<div id="gwou2g0" class="layui-carousel" id="right-item">
<div carousel-item>
<div>
<a href="http://m.specialneedsforspecialkids.com/site/active/kuaijiesale.html?ytag=seo" rel="nofollow">
<img src="http://m.specialneedsforspecialkids.com/yun/data/attach/240625/2rTjEHmi.png" alt="云服務器">
</a>
</div>
<div>
<a href="http://m.specialneedsforspecialkids.com/site/product/gpu.html" rel="nofollow">
<img src="http://m.specialneedsforspecialkids.com/yun/data/attach/240807/7NjZjdrd.png" alt="GPU云服務器">
</a>
</div>
</div>
</div>
</div> <!-- banner結束 -->
<div id="2kqmiys" class="adhtml">
</div>
<script>
$(function(){
$.ajax({
type: "GET",
url:"http://m.specialneedsforspecialkids.com/yun/ad/getad/1.html",
cache: false,
success: function(text){
$(".adhtml").html(text);
}
});
})
</script> </div> </div>
</div>
</div>
</div>
</section>
<!-- wap拉出按鈕 -->
<div id="uwo2o0a" class="site-tree-mobile layui-hide">
<i class="layui-icon layui-icon-spread-left"></i>
</div>
<!-- wap遮罩層 -->
<div id="isys0yq" class="site-mobile-shade"></div>
<!--付費閱讀 -->
<div class="eeasmqw" id="payread">
<div id="egayk2m" class="layui-form-item">閱讀需要支付1元查看</div>
<div id="cs0eqou" class="layui-form-item"><button class="btn-right">支付并查看</button></div>
</div>
<script>
var prei=0;
$(".site-seo-depict pre").each(function(){
var html=$(this).html().replace("<code>","").replace("</code>","").replace('<code class="javascript hljs" codemark="1">','');
$(this).attr('data-clipboard-text',html).attr("id","pre"+prei);
$(this).html("").append("<code>"+html+"</code>");
prei++;
})
$(".site-seo-depict img").each(function(){
if($(this).attr("src").indexOf('data:image/svg+xml')!= -1){
$(this).remove();
}
})
$("LINK[href*='style-49037e4d27.css']").remove();
$("LINK[href*='markdown_views-d7a94ec6ab.css']").remove();
layui.use(['jquery', 'layer','code'], function(){
$("pre").attr("class","layui-code");
$("pre").attr("lay-title","");
$("pre").attr("lay-skin","");
layui.code();
$(".layui-code-h3 a").attr("class","copycode").html("復制代碼 ").attr("onclick","copycode(this)");
});
function copycode(target){
var id=$(target).parent().parent().attr("id");
var clipboard = new ClipboardJS("#"+id);
clipboard.on('success', function(e) {
e.clearSelection();
alert("復制成功")
});
clipboard.on('error', function(e) {
alert("復制失敗")
});
}
//$(".site-seo-depict").html($(".site-seo-depict").html().slice(0, -5));
</script>
<link rel="stylesheet" type="text/css" href="http://m.specialneedsforspecialkids.com/yun/static/js/neweditor/code/styles/tomorrow-night-eighties.css">
<script src="http://m.specialneedsforspecialkids.com/yun/static/js/neweditor/code/highlight.pack.js" type="text/javascript"></script>
<script src="http://m.specialneedsforspecialkids.com/yun/static/js/clipboard.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
function setcode(){
var _html='';
document.querySelectorAll('pre code').forEach((block) => {
var _tmptext=$.trim($(block).text());
if(_tmptext!=''){
_html=_html+_tmptext;
console.log(_html);
}
});
}
</script>
<script>
function payread(){
layer.open({
type: 1,
title:"付費閱讀",
shadeClose: true,
content: $('#payread')
});
}
// 舉報
function jupao_tip(){
layer.open({
type: 1,
title:false,
shadeClose: true,
content: $('#jubao')
});
}
$(".getcommentlist").click(function(){
var _id=$(this).attr("dataid");
var _tid=$(this).attr("datatid");
$("#articlecommentlist"+_id).toggleClass("hide");
var flag=$("#articlecommentlist"+_id).attr("dataflag");
if(flag==1){
flag=0;
}else{
flag=1;
//加載評論
loadarticlecommentlist(_id,_tid);
}
$("#articlecommentlist"+_id).attr("dataflag",flag);
})
$(".add-comment-btn").click(function(){
var _id=$(this).attr("dataid");
$(".formcomment"+_id).toggleClass("hide");
})
$(".btn-sendartcomment").click(function(){
var _aid=$(this).attr("dataid");
var _tid=$(this).attr("datatid");
var _content=$.trim($(".commenttext"+_aid).val());
if(_content==''){
alert("評論內容不能為空");
return false;
}
var touid=$("#btnsendcomment"+_aid).attr("touid");
if(touid==null){
touid=0;
}
addarticlecomment(_tid,_aid,_content,touid);
})
$(".button_agree").click(function(){
var supportobj = $(this);
var tid = $(this).attr("id");
$.ajax({
type: "GET",
url:"http://m.specialneedsforspecialkids.com/yun/index.php?topic/ajaxhassupport/" + tid,
cache: false,
success: function(hassupport){
if (hassupport != '1'){
$.ajax({
type: "GET",
cache:false,
url: "http://m.specialneedsforspecialkids.com/yun/index.php?topic/ajaxaddsupport/" + tid,
success: function(comments) {
supportobj.find("span").html(comments+"人贊");
}
});
}else{
alert("您已經贊過");
}
}
});
});
function attenquestion(_tid,_rs){
$.ajax({
//提交數據的類型 POST GET
type:"POST",
//提交的網址
url:"http://m.specialneedsforspecialkids.com/yun/favorite/topicadd.html",
//提交的數據
data:{tid:_tid,rs:_rs},
//返回數據的格式
datatype: "json",//"xml", "html", "script", "json", "jsonp", "text".
//在請求之前調用的函數
beforeSend:function(){},
//成功返回之后調用的函數
success:function(data){
var data=eval("("+data+")");
console.log(data)
if(data.code==2000){
layer.msg(data.msg,function(){
if(data.rs==1){
//取消收藏
$(".layui-layer-tips").attr("data-tips","收藏文章");
$(".layui-layer-tips").html('<i class="fa fa-heart-o"></i>');
}
if(data.rs==0){
//收藏成功
$(".layui-layer-tips").attr("data-tips","已收藏文章");
$(".layui-layer-tips").html('<i class="fa fa-heart"></i>')
}
})
}else{
layer.msg(data.msg)
}
} ,
//調用執行后調用的函數
complete: function(XMLHttpRequest, textStatus){
postadopt=true;
},
//調用出錯執行的函數
error: function(){
//請求出錯處理
postadopt=false;
}
});
}
</script>
<footer>
<div id="qosy0wm" class="layui-container">
<div id="aqk0equ" class="flex_box_zd">
<div id="cqwgm02" class="left-footer">
<h6><a href="http://m.specialneedsforspecialkids.com/"><img src="http://m.specialneedsforspecialkids.com/yun/static/theme/ukd//images/logo.png" alt="UCloud (優刻得科技股份有限公司)"></a></h6>
<p>UCloud (優刻得科技股份有限公司)是中立、安全的云計算服務平臺,堅持中立,不涉足客戶業務領域。公司自主研發IaaS、PaaS、大數據流通平臺、AI服務平臺等一系列云計算產品,并深入了解互聯網、傳統企業在不同場景下的業務需求,提供公有云、混合云、私有云、專有云在內的綜合性行業解決方案。</p>
</div>
<div id="og20oci" class="right-footer layui-hidemd">
<ul class="flex_box_zd">
<li>
<h6>UCloud與云服務</h6>
<p><a href="http://m.specialneedsforspecialkids.com/site/about/intro/">公司介紹</a></p>
<p><a >加入我們</a></p>
<p><a href="http://m.specialneedsforspecialkids.com/site/ucan/onlineclass/">UCan線上公開課</a></p>
<p><a href="http://m.specialneedsforspecialkids.com/site/solutions.html" >行業解決方案</a></p> <p><a href="http://m.specialneedsforspecialkids.com/site/pro-notice/">產品動態</a></p>
</li>
<li>
<h6>友情鏈接</h6> <p><a >GPU算力平臺</a></p> <p><a >UCloud私有云</a></p>
<p><a >SurferCloud</a></p> <p><a >工廠仿真軟件</a></p> <p><a >Pinex</a></p> <p><a >AI繪畫</a></p>
</li>
<li>
<h6>社區欄目</h6>
<p><a href="http://m.specialneedsforspecialkids.com/yun/column/index.html">專欄文章</a></p>
<p><a href="http://m.specialneedsforspecialkids.com/yun/udata/">專題地圖</a></p> </li>
<li>
<h6>常見問題</h6>
<p><a href="http://m.specialneedsforspecialkids.com/site/ucsafe/notice.html" >安全中心</a></p>
<p><a href="http://m.specialneedsforspecialkids.com/site/about/news/recent/" >新聞動態</a></p>
<p><a href="http://m.specialneedsforspecialkids.com/site/about/news/report/">媒體動態</a></p> <p><a href="http://m.specialneedsforspecialkids.com/site/cases.html">客戶案例</a></p>
<p><a href="http://m.specialneedsforspecialkids.com/site/notice/">公告</a></p>
</li>
<li>
<span><img src="https://static.ucloud.cn/7a4b6983f4b94bcb97380adc5d073865.png" alt="優刻得"></span>
<p>掃掃了解更多</p></div>
</div>
<div id="wwskgoc" class="copyright">Copyright ? 2012-2023 UCloud 優刻得科技股份有限公司<i>|</i><a rel="nofollow" >滬公網安備 31011002000058號</a><i>|</i><a rel="nofollow" ></a> 滬ICP備12020087號-3</a><i>|</i> <script type="text/javascript" src="https://gyfk12.kuaishang.cn/bs/ks.j?cI=197688&fI=125915" charset="utf-8"></script>
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?290c2650b305fc9fff0dbdcafe48b59d";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-DZSMXQ3P9N"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-DZSMXQ3P9N');
</script>
<script>
(function(){
var el = document.createElement("script");
el.src = "https://lf1-cdn-tos.bytegoofy.com/goofy/ttzz/push.js?99f50ea166557aed914eb4a66a7a70a4709cbb98a54ecb576877d99556fb4bfc3d72cd14f8a76432df3935ab77ec54f830517b3cb210f7fd334f50ccb772134a";
el.id = "ttzz";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(el, s);
})(window)
</script></div>
</div>
</footer>
<footer>
<div class="friendship-link">
<p>感谢您访问我们的网站,您可能还对以下资源感兴趣:</p>
<a href="http://m.specialneedsforspecialkids.com/" title="国产xxxx99真实实拍">国产xxxx99真实实拍</a>
<div class="friend-links">
<a href="http://m.cp97744.com/">国产一区电影</a>
</div>
</div>
</footer>
<script>
(function(){
var bp = document.createElement('script');
var curProtocol = window.location.protocol.split(':')[0];
if (curProtocol === 'https') {
bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
}
else {
bp.src = 'http://push.zhanzhang.baidu.com/push.js';
}
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(bp, s);
})();
</script>
</body><div id="4goam" class="pl_css_ganrao" style="display: none;"><del id="4goam"></del><del id="4goam"></del><bdo id="4goam"></bdo><source id="4goam"><strong id="4goam"><optgroup id="4goam"></optgroup></strong></source><bdo id="4goam"><abbr id="4goam"><delect id="4goam"></delect></abbr></bdo><tr id="4goam"></tr><li id="4goam"></li><blockquote id="4goam"></blockquote><tr id="4goam"></tr><menu id="4goam"></menu><cite id="4goam"></cite><abbr id="4goam"></abbr><small id="4goam"></small><dl id="4goam"></dl><input id="4goam"></input><samp id="4goam"></samp><kbd id="4goam"></kbd><center id="4goam"></center><tr id="4goam"><wbr id="4goam"><cite id="4goam"></cite></wbr></tr><source id="4goam"><code id="4goam"><noframes id="4goam"></noframes></code></source><dd id="4goam"><dl id="4goam"><nav id="4goam"></nav></dl></dd><tr id="4goam"><tr id="4goam"><wbr id="4goam"></wbr></tr></tr><kbd id="4goam"></kbd><table id="4goam"></table><tbody id="4goam"></tbody><li id="4goam"></li><wbr id="4goam"><cite id="4goam"><table id="4goam"></table></cite></wbr><small id="4goam"></small><li id="4goam"></li><pre id="4goam"><td id="4goam"><fieldset id="4goam"></fieldset></td></pre><object id="4goam"></object><kbd id="4goam"></kbd><dl id="4goam"></dl><center id="4goam"><dd id="4goam"><th id="4goam"></th></dd></center><strong id="4goam"></strong><menu id="4goam"><tr id="4goam"><acronym id="4goam"></acronym></tr></menu><nav id="4goam"><abbr id="4goam"><sup id="4goam"></sup></abbr></nav><object id="4goam"></object><samp id="4goam"><tbody id="4goam"><object id="4goam"></object></tbody></samp><source id="4goam"><strong id="4goam"><optgroup id="4goam"></optgroup></strong></source><strike id="4goam"></strike><abbr id="4goam"></abbr><noscript id="4goam"></noscript><bdo id="4goam"></bdo><wbr id="4goam"></wbr><menu id="4goam"><tbody id="4goam"><pre id="4goam"></pre></tbody></menu><dd id="4goam"><th id="4goam"><object id="4goam"></object></th></dd><delect id="4goam"></delect><nav id="4goam"></nav><em id="4goam"><ul id="4goam"><dfn id="4goam"></dfn></ul></em><button id="4goam"></button><em id="4goam"></em><button id="4goam"></button><samp id="4goam"></samp><ul id="4goam"></ul><code id="4goam"></code><tbody id="4goam"><object id="4goam"><small id="4goam"></small></object></tbody><wbr id="4goam"></wbr><center id="4goam"><dd id="4goam"><dl id="4goam"></dl></dd></center><tr id="4goam"></tr><blockquote id="4goam"><tfoot id="4goam"><input id="4goam"></input></tfoot></blockquote><xmp id="4goam"></xmp><input id="4goam"></input><tbody id="4goam"><s id="4goam"><small id="4goam"></small></s></tbody><delect id="4goam"><tr id="4goam"><s id="4goam"></s></tr></delect><dl id="4goam"><nav id="4goam"><li id="4goam"></li></nav></dl><blockquote id="4goam"></blockquote><strong id="4goam"></strong><fieldset id="4goam"><table id="4goam"><tr id="4goam"></tr></table></fieldset><option id="4goam"><samp id="4goam"><tbody id="4goam"></tbody></samp></option><option id="4goam"><samp id="4goam"><tbody id="4goam"></tbody></samp></option><td id="4goam"></td><sup id="4goam"><center id="4goam"><strong id="4goam"></strong></center></sup><optgroup id="4goam"><ul id="4goam"><dfn id="4goam"></dfn></ul></optgroup><td id="4goam"></td><li id="4goam"></li><dl id="4goam"><optgroup id="4goam"><abbr id="4goam"></abbr></optgroup></dl><center id="4goam"><dl id="4goam"><optgroup id="4goam"></optgroup></dl></center><tr id="4goam"></tr><nav id="4goam"></nav><acronym id="4goam"></acronym><tbody id="4goam"></tbody><td id="4goam"></td><tbody id="4goam"><em id="4goam"><del id="4goam"></del></em></tbody><nav id="4goam"><abbr id="4goam"><button id="4goam"></button></abbr></nav><bdo id="4goam"><abbr id="4goam"><delect id="4goam"></delect></abbr></bdo><dd id="4goam"><dl id="4goam"><nav id="4goam"></nav></dl></dd><del id="4goam"></del><noscript id="4goam"></noscript><abbr id="4goam"><dfn id="4goam"><source id="4goam"></source></dfn></abbr><sup id="4goam"><center id="4goam"><dl id="4goam"></dl></center></sup><pre id="4goam"><xmp id="4goam"><strike id="4goam"></strike></xmp></pre><li id="4goam"></li><nav id="4goam"></nav><object id="4goam"></object><nav id="4goam"></nav><center id="4goam"></center><kbd id="4goam"></kbd><pre id="4goam"></pre><delect id="4goam"></delect><center id="4goam"></center><center id="4goam"></center><wbr id="4goam"><bdo id="4goam"><abbr id="4goam"></abbr></bdo></wbr><samp id="4goam"><th id="4goam"><object id="4goam"></object></th></samp><table id="4goam"><kbd id="4goam"><pre id="4goam"></pre></kbd></table><code id="4goam"></code><tfoot id="4goam"></tfoot><tr id="4goam"><pre id="4goam"><xmp id="4goam"></xmp></pre></tr><source id="4goam"></source><abbr id="4goam"></abbr><noframes id="4goam"></noframes><pre id="4goam"><td id="4goam"><cite id="4goam"></cite></td></pre><delect id="4goam"></delect><source id="4goam"><dl id="4goam"><optgroup id="4goam"></optgroup></dl></source><wbr id="4goam"></wbr><abbr id="4goam"><center id="4goam"><center id="4goam"></center></center></abbr><tfoot id="4goam"></tfoot><bdo id="4goam"></bdo><source id="4goam"></source><strike id="4goam"></strike><em id="4goam"><blockquote id="4goam"><tfoot id="4goam"></tfoot></blockquote></em><wbr id="4goam"><bdo id="4goam"><abbr id="4goam"></abbr></bdo></wbr><tbody id="4goam"></tbody><tr id="4goam"><wbr id="4goam"><bdo id="4goam"></bdo></wbr></tr><input id="4goam"></input><dl id="4goam"><nav id="4goam"><li id="4goam"></li></nav></dl><em id="4goam"><del id="4goam"><tfoot id="4goam"></tfoot></del></em><tfoot id="4goam"></tfoot><cite id="4goam"><table id="4goam"><tr id="4goam"></tr></table></cite><noframes id="4goam"></noframes><fieldset id="4goam"></fieldset><li id="4goam"></li><code id="4goam"><noframes id="4goam"><del id="4goam"></del></noframes></code><table id="4goam"><tr id="4goam"><pre id="4goam"></pre></tr></table><abbr id="4goam"></abbr><input id="4goam"></input><strong id="4goam"></strong><noframes id="4goam"></noframes><th id="4goam"><object id="4goam"><li id="4goam"></li></object></th><cite id="4goam"></cite><noscript id="4goam"></noscript><source id="4goam"><strong id="4goam"><optgroup id="4goam"></optgroup></strong></source><code id="4goam"></code><em id="4goam"><del id="4goam"><dfn id="4goam"></dfn></del></em><cite id="4goam"></cite><cite id="4goam"></cite><ul id="4goam"></ul><del id="4goam"></del><acronym id="4goam"></acronym><center id="4goam"></center></div>
<script src="http://m.specialneedsforspecialkids.com/yun/static/theme/ukd/js/common.js"></script>
<<script type="text/javascript">
$(".site-seo-depict *,.site-content-answer-body *,.site-body-depict *").css("max-width","100%");
</script>
</html>