摘要:關(guān)于標(biāo)題,為什么是愛與恨因?yàn)樵趧偝鰜淼臅r(shí)候,我并不是堅(jiān)定的支持者,有很多地方用起來不方便,設(shè)計(jì)不合理。用戶只有首次訪問需要下載全部靜態(tài)資源,以后的訪問都直接使用緩存資源。首先,在中添加字段,當(dāng)為時(shí),則開啟服務(wù)。例如請(qǐng)求的是則返回中的數(shù)據(jù)。
關(guān)于標(biāo)題,為什么是“愛與恨”?
因?yàn)樵?webpack 剛出來的時(shí)候,我并不是堅(jiān)定的支持者,有很多地方用起來不方便,api 設(shè)計(jì)不合理。隨著 webpack 和 react 生態(tài)的越發(fā)完善,加上 webpack2.0 的發(fā)布,它的功能也越來越強(qiáng)大,讓我又重新認(rèn)識(shí)它。
內(nèi)容提要 webpack 構(gòu)建方案webpack 生態(tài)
需求是什么
對(duì)比其他方案
webpack vs gulpwebpack
gulp
什么時(shí)候用
webpack 構(gòu)建方案 webpack 生態(tài)網(wǎng)上有好多介紹 webpack 的文章,本文只簡單介紹兩個(gè)基本概念。
Loaders
Plugins
Loaderswebpack 可以使用 loader 來處理文件,允許你打包除 JavaScript 之外的任何靜態(tài)資源。
raw-loader 加載文件原始內(nèi)容
file-loader 將文件發(fā)送到輸出文件夾,并返回 URL
url-loader 像 file loader 一樣工作,但如果文件小于限制,可以返回 data URL
babel-loader 加載 ES2015+ 代碼,然后使用 Babel 轉(zhuǎn)譯為 ES5
traceur-loader 加載 ES2015+ 代碼,然后使用 Traceur 轉(zhuǎn)譯為 ES5
ts-loader 像 JavaScript 一樣加載 TypeScript 2.0+
coffee-loader 像 JavaScript 一樣加載 CoffeeScript
html-loader 導(dǎo)出 HTML 為字符串,需要引用靜態(tài)資源
markdown-loader 將 Markdown 轉(zhuǎn)譯為 HTML
handlebars-loader 將 Handlebars 轉(zhuǎn)譯為 HTML
css-loader 解析 CSS 文件后,使用 import 加載,并且返回 CSS 代碼
less-loader 加載和轉(zhuǎn)譯 LESS 文件
sass-loader 加載和轉(zhuǎn)譯 SASS/SCSS 文件
postcss-loader 使用 PostCSS 加載和轉(zhuǎn)譯 CSS/SSS 文件
Plugins
CommonsChunkPlugin
將多個(gè)入口起點(diǎn)之間共享的公共模塊,生成為一些 chunk,并且分離到多帶帶的 bundle 中,例如,vendor.bundle.js 和 app.bundle.js
DefinePlugin, EnvironmentPlugin
允許在編譯時(shí)(compile time)配置的全局常量,用于允許「開發(fā)/發(fā)布」構(gòu)建之間的不同行為
ExtractTextWebpackPlugin
從 bundle 中提取 CSS 文本到獨(dú)立的文件
HtmlWebpackPlugin
用于簡化 HTML 文件(index.html)的創(chuàng)建,提供訪問 bundle 的服務(wù)。
I18nWebpackPlugin
為 bundle 增加國際化支持
NamedModulesPlugin
保留編譯結(jié)果的模塊名,便于調(diào)試
需求是什么技術(shù)問題還是要從需求出發(fā),我們團(tuán)隊(duì)的實(shí)際需求是什么。
區(qū)分兩套環(huán)境
多頁面多入口
mock 接口數(shù)據(jù)
iconfont 字體打包
1. 區(qū)分兩套環(huán)境develop
production
/build 文件夾編譯結(jié)果如下:
/build - /develop - /pageA - index.js - index.css - index.html - /pageB - common.js - common.css - /production - /1.0.0 - /pageA - index.js - index.css - index.html - /pageB - common.js - common.css - /1.0.1 - /pageA - index.js - index.css - index.html - /pageB - common.js - common.css
其中開發(fā)環(huán)境的 html 編譯結(jié)果為:
首頁
其中生產(chǎn)環(huán)境的 html 編譯結(jié)果為:
首頁
生產(chǎn)環(huán)境的最終頁面上靜態(tài)資源路徑是
業(yè)界還有一種做法是使用 /path/file.{hash}.js 形式的路徑,都是為了配合 http 緩存策略,做到前端資源的緩存和無縫發(fā)布。
緩存策略:在 http 響應(yīng)頭中,設(shè)置 Cache-Control、 Expires 和 Last-Modified 控制靜態(tài)資源緩存。用戶只有首次訪問需要下載全部靜態(tài)資源,以后的訪問都直接使用緩存資源。
cache-control:max-age=31536000 expires:Fri, 06 Apr 2018 08:32:17 GMT last-modified:Thu, 06 Apr 2017 06:54:04 GMT
對(duì)用戶和客戶端來說,每次發(fā)布更新代碼,只需要下載新的資源,而無需清除緩存
無縫發(fā)布:在團(tuán)隊(duì)合作開發(fā)中,往往會(huì)遇到新老版本兼容和發(fā)布順序的問題。例如原來的 a.js 添加了新功能變成了 a".js,為了避免前端先發(fā)布上線的代碼影響線上業(yè)務(wù),保證發(fā)布上去的代碼兼容舊版本,需要在代碼中添加如下的兼容語句:
if(newVersion) { // new feature } else { // old feature }
而使用增量發(fā)布的方式不需要這樣的額外處理,由于每次都是生成新的 url,那么只要后端引用的路徑?jīng)]有變化,就始終引用舊資源,一旦后端發(fā)布完成就自動(dòng)引用新資源。
多頁面多入口雖然 React 天然是開發(fā) SPA 的利器,它的生態(tài)中的配套工具也是以解決 SPA 問題為主,例如 React-Redux, React-Saga 等,但是我們的項(xiàng)目中頁面非常簡單,沒有太多的用戶交互,沒有太多的組件間的消息傳遞,如果強(qiáng)行引入這些概念,反而把整個(gè)項(xiàng)目搞得非常復(fù)雜,因此選用多頁面多入口的方案。使用前面提到的 HtmlWebpackPlugin 插件進(jìn)行多頁面的構(gòu)建。
// 編譯 html,多頁面多入口 Object.keys(webpackConfig.entry).forEach(name => { webpackConfig.plugins.push(new HtmlWebpackPlugin({ template: `./src/template.ejs`, filename: `${name}.html`, chunks: ["common", name] })); });mock 接口數(shù)據(jù)
webpack-dev-server 提供了 proxy 代理解決方案,但是沒有解決 mock 接口數(shù)據(jù)的問題。我們利用 Koa 開發(fā)了一個(gè)簡版的 mock 服務(wù)器,可以加載本地文件中的模擬測試數(shù)據(jù)。
首先,在 package.json 中添加 mockEnable 字段,當(dāng) mockEnable 為 true 時(shí),則開啟 mock 服務(wù)。
// package.json { "mockEnable": true, "proxy": { "/api/**": { "target": "http://example.com" } }, }
其次,在 webpack.config.js 中,將 package.proxy 的 target 指向 mock 服務(wù)器
// webpack.config.js const pkg = require("./package.json"); const MockServer = require("./mock/server.js"); // 將 http://example.com/api/path 的接口,轉(zhuǎn)發(fā)到 http://localhost:8088/api/path if(pkg.mockEnable) { let port = 8088; MockServer.start(port); Object.keys(pkg.proxy).forEach(filter => { let proxy = pkg.proxy[filter]; proxy.target = `http://localhost:${port}`; // mock server }); }
最后,在 mock 服務(wù)器中處理請(qǐng)求,mock server 的邏輯很簡單,只有不到 50 行代碼:
// mock/server.js const fs = require("fs"); const path = require("path"); const color = require("colorful"); const Koa = require("koa"); const router = require("koa-router")(); let app = new Koa(); /* 訪問日志,logger */ app.use(async function (ctx, next) { console.log(color.green("Mock Server"), (new Date()).toLocaleString(), "url:", ctx.url); await next(); }); /* 路由,router */ router.all("*", async (ctx) => { let filepath = path.resolve(__dirname, "data", `./${ctx.url}.json`); let methodFilepath = path.resolve(__dirname, "data", `./${ctx.url}.${ctx.method}.json`); if(fs.existsSync(filepath)) { ctx.body = require(filepath); } else if(fs.existsSync(methodFilepath)) { ctx.body = require(methodFilepath); } else { ctx.status = 404; ctx.body = "Mock data not found"; } }); app.use(router.routes()).use(router.allowedMethods()); /* 啟動(dòng) Mock Server */ exports.start = function(port) { app.listen(port, () => { console.log("Mock Server started on", color.green(`http://127.0.0.1:${port}/`)); }); return app; }
最終的效果是,只要開啟了 mockEnable,當(dāng)用戶在頁面上發(fā)起請(qǐng)求時(shí),自動(dòng)返回本地文件中的模擬數(shù)據(jù)。例如請(qǐng)求的是GET http://localhost/api/path/users則返回/mock/data/api/path/users.json
中的數(shù)據(jù)。如果同一個(gè) url 既有 GET 請(qǐng)求又有 POST 請(qǐng)求,則可以通過如下方式避免沖突
/mock/data/api/path/users.get.json /mock/data/api/path/users.post.jsoniconfont 字體打包
我們的項(xiàng)目是基于 Ant Design 做二次開發(fā),由于 ant design 的 iconfont 會(huì)依賴 https://at.alicdn.com/ 的字體資源,而公司內(nèi)網(wǎng)不能訪問外部資源,所以需要將字體打包到自己的應(yīng)用里。利用 less-loader 的 modifyVars 屬性,修改 antd 里的 @icon-url 變量。
先設(shè)置 @icon-url 的變量值。
// webpack.config.js let cssVars = { "@icon-url": ""/assets/iconfont/iconfont"", }
然后在 less-loader 的參數(shù)中設(shè)置 modifyVars。
// webpack.config.js { test: /.less$/, use: ExtractTextPlugin.extract([ "css-loader", { loader:"less-loader", options: { modifyVars:cssVars } } ]) }對(duì)比其他方案
atool-build
預(yù)設(shè)了適用于 antd 的 webpack 構(gòu)建腳本
配置非常靈活,幾乎支持所有場景的定制
對(duì)插件的修改略麻煩
版本升級(jí)兼容性不太好
roadhog
使用非常簡單,不用關(guān)心那么多概念
自定義的靈活性受局限
Webpack vs Gulp先看看 webpack 和 gulp 各自的官方說明:
Webpack
webpack is a module bundler for modern JavaScript applications.
Gulp
gulp is a toolkit for automating painful or time-consuming tasks in your development workflow, so you can stop messing around and build something.
從上述官方描述可以看出,webpack 的核心概念是 module bundler,而 gulp 的核心概念是 toolkit for tasks。一個(gè)側(cè)重模塊打包,一個(gè)側(cè)重自動(dòng)化任務(wù)處理。
webpack - 一切皆是模塊從官方網(wǎng)站上的圖片可以看出,一切皆是模塊,不管是 js 模塊,還是 css、sass 樣式,還是模板(hds)、圖片(jpg/png)、字體等資源,都以被 webpack 當(dāng)做互相依賴的模塊(modules with dependencies)處理。經(jīng)過 loader 的解析,最終處理成頁面上可用的靜態(tài)資源(static assets)。由于模塊間需要有互相依賴關(guān)系,因此需要在 js 里 require 樣式和圖片等資源。
蛋疼的 api,webpack loader 的參數(shù)形式簡直反人類,這種字符串拼接的方式不直觀且不方便擴(kuò)展
require("file-loader?name=js/[hash].script.[ext]!./javascript.js"); require("file-loader?name=html-[hash:6].html!./page.html"); require("file-loader?name=[hash]!./flash.txt"); require("file-loader?name=[sha512:hash:base64:7].[ext]!./image.png"); require("file-loader?name=img-[sha512:hash:base64:7].[ext]!./image.jpg"); require("file-loader?name=picture.png!./myself.png"); require("file-loader?name=[path][name].[ext]?[hash]!./dir/file.png")
以下是一個(gè)簡單的 webpack 的例子:
// webpack.config.js const path = require("path"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const CleanWebpackPlugin = require("clean-webpack-plugin"); const UglifyJSPlugin = require("uglifyjs-webpack-plugin"); const ExtractTextPlugin = require("extract-text-webpack-plugin"); let webpackConfig = { entry: { index: "./src/index.js" }, output: { path: path.resolve(__dirname, "./build"), filename: "[name].js", publicPath: "./" }, module: { rules: [{ test: /.jsx?$/, exclude: /node_modules/, use: { loader: "babel-loader" } }, { test: /.less$/, use: ExtractTextPlugin.extract([ "css-loader", "less-loader" ]) }, { test: /.(png|jpg|jpeg|gif)$/i, use: { loader:"file-loader", options: { name: "static/images/[name].[ext]" } } }, { test: /.(woff2?|ttf|eot|svg)$/, use: { loader:"file-loader", options: { name: "static/fonts/[name].[ext]" } } }] }, plugins: [ new ExtractTextPlugin("[name].css"), new UglifyJSPlugin(), new CleanWebpackPlugin(["./build"]), new HtmlWebpackPlugin({ template: "./src/index.html", filename: "index.html", chunks: ["index"], title: "首頁", message: "webpack 測試頁面" }) ] }; module.exports = webpackConfig;gulp - 一切基于任務(wù)
gulp 是作為一個(gè) task runner 存在的,最核心的功能是自動(dòng)化任務(wù)執(zhí)行,復(fù)雜任務(wù)組織,基于文件 stream 的構(gòu)建,加上完善的插件體系,處理各種類型的任務(wù)執(zhí)行流程。用戶可以預(yù)先定義好一系列的 task,定義好這些 task 分別做些什么,然后定義好執(zhí)行順序,最后由 gulp 來執(zhí)行這些 task。所以 gulp 可以做到幾乎所有 node 能做到的事情,不僅僅是用來打包 js。
下面是一個(gè)簡單的 gulp 的例子:
// gulpfile.js const gulp = require("gulp"); const clean = require("del"); const ejs = require("gulp-ejs"); const less = require("gulp-less"); const jsmin = require("gulp-jsmin"); const minifyCSS = require("gulp-csso"); gulp.task("html", function(){ return gulp.src("src/*.html") .pipe(ejs({ title: "首頁", message: "gulp 測試頁面" })) .pipe(gulp.dest("build")) }); gulp.task("css", function(){ return gulp.src("src/*.less") .pipe(less()) .pipe(minifyCSS()) .pipe(gulp.dest("build")) }); gulp.task("js", function () { gulp.src(["src/*.js"]) .pipe(jsmin()) .pipe(gulp.dest("build")) }); gulp.task("clean", function () { clean(["build"]); }); gulp.task("clone", function () { gulp.src(["static/**"]) .pipe(gulp.dest("build/static/")) }); gulp.task("default", ["clean", "clone", "html", "css", "js" ]);webpack vs gulp
下面對(duì)比 webpack 和 gulp 各自適合的場景
webpack
基于模塊依賴的打包構(gòu)建
模塊切割
公共模塊提取
gulp
與模塊化無關(guān)的構(gòu)建過程
js / css 批量壓縮
圖片壓縮
批量文本替換
復(fù)雜的任務(wù)處理
項(xiàng)目發(fā)布任務(wù)
啟動(dòng)服務(wù)器
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/83162.html
摘要:原文地址從實(shí)踐到原理,帶你參透在語言中大放異彩,越來越多的小伙伴在使用,最近也在公司安利了一波,希望能通過這篇文章能帶你一覽的愛與恨。幀的主要作用是裝填主體信息,是數(shù)據(jù)幀。 showImg(https://segmentfault.com/img/remote/1460000019552245); 原文地址:從實(shí)踐到原理,帶你參透 gRPC gRPC 在 Go 語言中大放異彩,越來越多...
摘要:順便一說,這首歌的原唱是秋田,中島當(dāng)年嗓子壞了,才有這歌。中文是直接翻譯來的,作曲是秋田。一部電影春夏秋冬又一春春夏秋冬又一春是由金基德執(zhí)導(dǎo),金英民吳英秀金基德主演的一部韓國電影。年月日于韓國上映。 原鏈接: http://bluezhan.me/weekly/#/9-2 1、web前端 Angular vs. React vs. Vue: A 2017 comparison 9 S...
摘要:順便一說,這首歌的原唱是秋田,中島當(dāng)年嗓子壞了,才有這歌。中文是直接翻譯來的,作曲是秋田。一部電影春夏秋冬又一春春夏秋冬又一春是由金基德執(zhí)導(dǎo),金英民吳英秀金基德主演的一部韓國電影。年月日于韓國上映。 原鏈接: http://bluezhan.me/weekly/#/9-2 1、web前端 Angular vs. React vs. Vue: A 2017 comparison 9 S...
摘要:胡凱,運(yùn)維負(fù)責(zé)人,曾經(jīng)就職于金山軟件金山網(wǎng)絡(luò)獵豹移動(dòng),負(fù)責(zé)運(yùn)維相關(guān)工作。胡凱在去年加入站剛剛成立的運(yùn)維部,人少事多,遇到了很多坑。 胡凱,bilibili運(yùn)維負(fù)責(zé)人,曾經(jīng)就職于金山軟件、金山網(wǎng)絡(luò)、獵豹移動(dòng),負(fù)責(zé)運(yùn)維相關(guān)工作。Bilibili是國內(nèi)最大的年輕人潮流文化娛樂社區(qū),銀河系知名彈幕視頻分享UGC平臺(tái)。 95后二次元新人類的追捧,讓以視頻彈幕、UP主聞名于世的bilibili(...
閱讀 687·2023-04-25 18:59
閱讀 1226·2021-09-22 16:00
閱讀 1897·2021-09-22 15:42
閱讀 3606·2021-09-22 15:27
閱讀 1257·2019-08-30 15:54
閱讀 1113·2019-08-30 11:16
閱讀 2460·2019-08-29 16:24
閱讀 836·2019-08-29 12:14