摘要:關于標題,為什么是愛與恨因為在剛出來的時候,我并不是堅定的支持者,有很多地方用起來不方便,設計不合理。用戶只有首次訪問需要下載全部靜態資源,以后的訪問都直接使用緩存資源。首先,在中添加字段,當為時,則開啟服務。例如請求的是則返回中的數據。
關于標題,為什么是“愛與恨”?
因為在 webpack 剛出來的時候,我并不是堅定的支持者,有很多地方用起來不方便,api 設計不合理。隨著 webpack 和 react 生態的越發完善,加上 webpack2.0 的發布,它的功能也越來越強大,讓我又重新認識它。
內容提要 webpack 構建方案webpack 生態
需求是什么
對比其他方案
webpack vs gulpwebpack
gulp
什么時候用
webpack 構建方案 webpack 生態網上有好多介紹 webpack 的文章,本文只簡單介紹兩個基本概念。
Loaders
Plugins
Loaderswebpack 可以使用 loader 來處理文件,允許你打包除 JavaScript 之外的任何靜態資源。
raw-loader 加載文件原始內容
file-loader 將文件發送到輸出文件夾,并返回 URL
url-loader 像 file loader 一樣工作,但如果文件小于限制,可以返回 data URL
babel-loader 加載 ES2015+ 代碼,然后使用 Babel 轉譯為 ES5
traceur-loader 加載 ES2015+ 代碼,然后使用 Traceur 轉譯為 ES5
ts-loader 像 JavaScript 一樣加載 TypeScript 2.0+
coffee-loader 像 JavaScript 一樣加載 CoffeeScript
html-loader 導出 HTML 為字符串,需要引用靜態資源
markdown-loader 將 Markdown 轉譯為 HTML
handlebars-loader 將 Handlebars 轉譯為 HTML
css-loader 解析 CSS 文件后,使用 import 加載,并且返回 CSS 代碼
less-loader 加載和轉譯 LESS 文件
sass-loader 加載和轉譯 SASS/SCSS 文件
postcss-loader 使用 PostCSS 加載和轉譯 CSS/SSS 文件
Plugins
CommonsChunkPlugin
將多個入口起點之間共享的公共模塊,生成為一些 chunk,并且分離到多帶帶的 bundle 中,例如,vendor.bundle.js 和 app.bundle.js
DefinePlugin, EnvironmentPlugin
允許在編譯時(compile time)配置的全局常量,用于允許「開發/發布」構建之間的不同行為
ExtractTextWebpackPlugin
從 bundle 中提取 CSS 文本到獨立的文件
HtmlWebpackPlugin
用于簡化 HTML 文件(index.html)的創建,提供訪問 bundle 的服務。
I18nWebpackPlugin
為 bundle 增加國際化支持
NamedModulesPlugin
保留編譯結果的模塊名,便于調試
需求是什么技術問題還是要從需求出發,我們團隊的實際需求是什么。
區分兩套環境
多頁面多入口
mock 接口數據
iconfont 字體打包
1. 區分兩套環境develop
production
/build 文件夾編譯結果如下:
/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
其中開發環境的 html 編譯結果為:
首頁
其中生產環境的 html 編譯結果為:
首頁
生產環境的最終頁面上靜態資源路徑是
業界還有一種做法是使用 /path/file.{hash}.js 形式的路徑,都是為了配合 http 緩存策略,做到前端資源的緩存和無縫發布。
緩存策略:在 http 響應頭中,設置 Cache-Control、 Expires 和 Last-Modified 控制靜態資源緩存。用戶只有首次訪問需要下載全部靜態資源,以后的訪問都直接使用緩存資源。
cache-control:max-age=31536000 expires:Fri, 06 Apr 2018 08:32:17 GMT last-modified:Thu, 06 Apr 2017 06:54:04 GMT
對用戶和客戶端來說,每次發布更新代碼,只需要下載新的資源,而無需清除緩存
無縫發布:在團隊合作開發中,往往會遇到新老版本兼容和發布順序的問題。例如原來的 a.js 添加了新功能變成了 a".js,為了避免前端先發布上線的代碼影響線上業務,保證發布上去的代碼兼容舊版本,需要在代碼中添加如下的兼容語句:
if(newVersion) { // new feature } else { // old feature }
而使用增量發布的方式不需要這樣的額外處理,由于每次都是生成新的 url,那么只要后端引用的路徑沒有變化,就始終引用舊資源,一旦后端發布完成就自動引用新資源。
多頁面多入口雖然 React 天然是開發 SPA 的利器,它的生態中的配套工具也是以解決 SPA 問題為主,例如 React-Redux, React-Saga 等,但是我們的項目中頁面非常簡單,沒有太多的用戶交互,沒有太多的組件間的消息傳遞,如果強行引入這些概念,反而把整個項目搞得非常復雜,因此選用多頁面多入口的方案。使用前面提到的 HtmlWebpackPlugin 插件進行多頁面的構建。
// 編譯 html,多頁面多入口 Object.keys(webpackConfig.entry).forEach(name => { webpackConfig.plugins.push(new HtmlWebpackPlugin({ template: `./src/template.ejs`, filename: `${name}.html`, chunks: ["common", name] })); });mock 接口數據
webpack-dev-server 提供了 proxy 代理解決方案,但是沒有解決 mock 接口數據的問題。我們利用 Koa 開發了一個簡版的 mock 服務器,可以加載本地文件中的模擬測試數據。
首先,在 package.json 中添加 mockEnable 字段,當 mockEnable 為 true 時,則開啟 mock 服務。
// package.json { "mockEnable": true, "proxy": { "/api/**": { "target": "http://example.com" } }, }
其次,在 webpack.config.js 中,將 package.proxy 的 target 指向 mock 服務器
// webpack.config.js const pkg = require("./package.json"); const MockServer = require("./mock/server.js"); // 將 http://example.com/api/path 的接口,轉發到 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 服務器中處理請求,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()); /* 啟動 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,當用戶在頁面上發起請求時,自動返回本地文件中的模擬數據。例如請求的是GET http://localhost/api/path/users則返回/mock/data/api/path/users.json
中的數據。如果同一個 url 既有 GET 請求又有 POST 請求,則可以通過如下方式避免沖突
/mock/data/api/path/users.get.json /mock/data/api/path/users.post.jsoniconfont 字體打包
我們的項目是基于 Ant Design 做二次開發,由于 ant design 的 iconfont 會依賴 https://at.alicdn.com/ 的字體資源,而公司內網不能訪問外部資源,所以需要將字體打包到自己的應用里。利用 less-loader 的 modifyVars 屬性,修改 antd 里的 @icon-url 變量。
先設置 @icon-url 的變量值。
// webpack.config.js let cssVars = { "@icon-url": ""/assets/iconfont/iconfont"", }
然后在 less-loader 的參數中設置 modifyVars。
// webpack.config.js { test: /.less$/, use: ExtractTextPlugin.extract([ "css-loader", { loader:"less-loader", options: { modifyVars:cssVars } } ]) }對比其他方案
atool-build
預設了適用于 antd 的 webpack 構建腳本
配置非常靈活,幾乎支持所有場景的定制
對插件的修改略麻煩
版本升級兼容性不太好
roadhog
使用非常簡單,不用關心那么多概念
自定義的靈活性受局限
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。一個側重模塊打包,一個側重自動化任務處理。
webpack - 一切皆是模塊從官方網站上的圖片可以看出,一切皆是模塊,不管是 js 模塊,還是 css、sass 樣式,還是模板(hds)、圖片(jpg/png)、字體等資源,都以被 webpack 當做互相依賴的模塊(modules with dependencies)處理。經過 loader 的解析,最終處理成頁面上可用的靜態資源(static assets)。由于模塊間需要有互相依賴關系,因此需要在 js 里 require 樣式和圖片等資源。
蛋疼的 api,webpack loader 的參數形式簡直反人類,這種字符串拼接的方式不直觀且不方便擴展
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")
以下是一個簡單的 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 - 一切基于任務
gulp 是作為一個 task runner 存在的,最核心的功能是自動化任務執行,復雜任務組織,基于文件 stream 的構建,加上完善的插件體系,處理各種類型的任務執行流程。用戶可以預先定義好一系列的 task,定義好這些 task 分別做些什么,然后定義好執行順序,最后由 gulp 來執行這些 task。所以 gulp 可以做到幾乎所有 node 能做到的事情,不僅僅是用來打包 js。
下面是一個簡單的 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
下面對比 webpack 和 gulp 各自適合的場景
webpack
基于模塊依賴的打包構建
模塊切割
公共模塊提取
gulp
與模塊化無關的構建過程
js / css 批量壓縮
圖片壓縮
批量文本替換
復雜的任務處理
項目發布任務
啟動服務器
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83162.html
摘要:原文地址從實踐到原理,帶你參透在語言中大放異彩,越來越多的小伙伴在使用,最近也在公司安利了一波,希望能通過這篇文章能帶你一覽的愛與恨。幀的主要作用是裝填主體信息,是數據幀。 showImg(https://segmentfault.com/img/remote/1460000019552245); 原文地址:從實踐到原理,帶你參透 gRPC gRPC 在 Go 語言中大放異彩,越來越多...
摘要:順便一說,這首歌的原唱是秋田,中島當年嗓子壞了,才有這歌。中文是直接翻譯來的,作曲是秋田。一部電影春夏秋冬又一春春夏秋冬又一春是由金基德執導,金英民吳英秀金基德主演的一部韓國電影。年月日于韓國上映。 原鏈接: http://bluezhan.me/weekly/#/9-2 1、web前端 Angular vs. React vs. Vue: A 2017 comparison 9 S...
摘要:順便一說,這首歌的原唱是秋田,中島當年嗓子壞了,才有這歌。中文是直接翻譯來的,作曲是秋田。一部電影春夏秋冬又一春春夏秋冬又一春是由金基德執導,金英民吳英秀金基德主演的一部韓國電影。年月日于韓國上映。 原鏈接: http://bluezhan.me/weekly/#/9-2 1、web前端 Angular vs. React vs. Vue: A 2017 comparison 9 S...
摘要:胡凱,運維負責人,曾經就職于金山軟件金山網絡獵豹移動,負責運維相關工作。胡凱在去年加入站剛剛成立的運維部,人少事多,遇到了很多坑。 胡凱,bilibili運維負責人,曾經就職于金山軟件、金山網絡、獵豹移動,負責運維相關工作。Bilibili是國內最大的年輕人潮流文化娛樂社區,銀河系知名彈幕視頻分享UGC平臺。 95后二次元新人類的追捧,讓以視頻彈幕、UP主聞名于世的bilibili(...
閱讀 678·2023-04-25 18:59
閱讀 1216·2021-09-22 16:00
閱讀 1891·2021-09-22 15:42
閱讀 3598·2021-09-22 15:27
閱讀 1252·2019-08-30 15:54
閱讀 1108·2019-08-30 11:16
閱讀 2450·2019-08-29 16:24
閱讀 826·2019-08-29 12:14