摘要:关于标题,为什么是爱与恨因为在刚出来的时候,我并不是坚定的支持者,有很多地方用起来不方便,设计不合理。用户只有首次访问需要下载全部静态资源,以后的访问都直接使用缓存资源。首先,在中添加字段,当为时,则开启服务。例如请求的是则返回中的数据。
关于标题,为什么是“爱与恨”?
因为在 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 批量压缩
图片压缩
批量文本替换
复杂的任务处理
项目发布任务
启动服务器
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/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(...
阅读 636·2023-04-25 18:59
阅读 1188·2021-09-22 16:00
阅读 1847·2021-09-22 15:42
阅读 3576·2021-09-22 15:27
阅读 1230·2019-08-30 15:54
阅读 1068·2019-08-30 11:16
阅读 2421·2019-08-29 16:24
阅读 784·2019-08-29 12:14