-
Notifications
You must be signed in to change notification settings - Fork 117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
基于webpack搭建前端工程解决方案探索 #10
Comments
先占位,学习学习 |
赞!持续关注!敢问作者大概啥时候会出下篇? |
占位,学习 |
webpack可以通过 {
entry: {
a: './js/pages/a.js',
b: './js/pages/b.js',
c: './js/pages/c.js',
d: './js/pages/d.js'
},
...
plugins: [
// 提取公共依赖模块,这种适合提取共享的基础库,如jquery、underscore等
new CommonsChunkPlugin('lib', ['a', 'b', 'c', 'd'], 4),
// 提取a、b、c中至少两个共同依赖的模块
new CommonsChunkPlugin('common-abc', ['a', 'b', 'c'], 2),
// 提取a、d共同依赖的模块
new CommonsChunkPlugin('common-ad', ['a', 'd']);
]
} 更多可以参考multiple commons chunks 静态分析打包是事先生成chunk,如果需要消除模块冗余,只能自行配置来提取,所以webpack也是有局限性和学习成本。 对于按需加载,比如你提到的这种场景,p1-p4就当做chunk来加载了: // app.js
switch(hash) {
// index#p1
case 'p1':
require.ensure([], function() {
require('p1');
});
break;
// index#p2
case 'p2':
require.ensure([], function() {
require('p2');
});
break;
...
// 依次类推
} 打包完成之后,p1到p4会生成名为[hash].chunk.js的一系列文件。 |
我应该是理解webpack的做法的,只是你可能没有注意到我的例子是一个怎样的陷阱:
单独看我例子中的a、b依赖c的情况,这个说法是正确的。
单独看我例子中的app加载p1-p4,这个说法也是正确的。 但是,把二者结合起来,就不是那么回事了。。。 因为p1-p4各自为一个chunk,其结果就是p1-p4不用处理,就是文件本身就行了;而ab抽取公共依赖c,也等价于三个文件不用处理,自然就是“c为ab的公共chunk”,在静态分析的模式下,这个例子的最优合并结果居然是不合并! 现实中这样的例子其实更多,而且会更复杂,静态分析面对大工程最终的结果往往是要么因为其局限性而根本配不出来合理的方案,要么因为配置太多维护成本过高而变成一个bundle的情况,没有真正的优化空间,那些“有公共依赖抽取插件从而进行优化”的假设基本形同虚设。 |
看过很多号称使用了webpack的项目,基本上无外乎这么几种最终运行效果:
正如 @kerryChen95 在我的blog底下留言说到的:
我觉得这种认识是早晚的,因为优势太明显了吧。。。 此外,我还想吐槽webpack的一些设计:
在三种语言中分别提供资源定位、资源内嵌、依赖声明的构建标识,采用表+资源加载框架优化加载,以上这两点构成了fis的核心设计理念,不可否认webpack是目前最接近fis的构建工具 |
@fouber 想象在某种理想情况下,静态资源构建的结果不是合并文件(资源内嵌除外),而是分析资源之间的依赖并且有工具去识别这种依赖关系,在资源下载的时候支持combine...... 所以“资源表+资源加载框架”这个概念我算是理解通了,看来要去学习下FIS。 不过关于webpack require 的设计: 1.require就是一个标识符,至于资源如何加载(或依赖标记)的,看对应的loader怎么实现。 |
为方便同步、打包,都走的本地文件形式,在webpack里引用非本地文件只能通过externals来实现么,但这种情况下希望实现懒加载又不行了(require.ensure内还是必须使用的本地模块或提前下载好了的模块)。 像我司大部分移动端项目都会把公共模块(CMD模式)投放到分发的CDN上(服务器配置的资源过期时间很短也不建议修改之,webpack 的文件hash形式不太适用),客户端第一次则下载并存入localstorage,下次则直接从本地取,这种缓存模式走 webpack 是否有办法实现? 这样就忍痛舍弃了seajs的依赖就近模式(比如可以使用 不知道对于我描述的问题是否有好的解决思路,我暂时也只想着留给后续的新项目用新的开发模式来走webpack,但这样新旧两种开发模式,开发和维护成本都有点高(无奈脸) |
参考下这个issue webpack是一个模块打包器,不是文件加载器,它只能加载本地磁盘上的文件。对于你们的情况,CDN的公共lib可以采用scriptjs加载,因为本地还需要判断localstorage,所以需要先判断local再加载,代码类似: if(localStorage.getItem('lib-content')) {
// todo
} else {
var $script = require("scriptjs");
$script('http://cdn.yourdomain.com/lib/jquery.js', function() {
// todo
});
} 而这段代码就可以放到入口文件里使用webpack打包。 对于公共业务module,个人觉得还是在开发时存放到本地比较好,开发完可以发布到npm或者公司内部模块管理平台。 |
谁能跟我说说,issue能收藏吗,小白白路过。。 |
@lilJay-lin 你在这里回复了之后,以后有新的回复时你会收到通知的 |
大牛,有个问题请教。我正在使用webpack做构建,我有多个entry,这时output的目录能指定多个,达到一对一的效果吗?就是'/src/common/a.js' --> '/dist/common/a.bundle.js','/src/page/b.js' --> '/dist/page/a.bundle.js'。请问大牛这种现在webpack能实现吗? |
可以的,参考官方文档吧 |
@FrendEr 这种好像做不到,filename项只支持字符串。 |
@io3 有例子吗 我没这样做过哎 一般无需关注webpack输出的结果。 |
@io3 对啊,有没有线上的例子或者教程呀?有点急,在线等哈! |
具体还可参考 http://webpack.github.io/docs/multiple-entry-points.html 页面底部的三种示例 |
@io3 这里最终打包的都是到了 /yourpath/dist/下面而已吧,都是同级的。我需要的是根据入口文件的路径来确定输出文件的路径哦 |
我发的code中, |
@io3 你的示例明显达不到他的需求啦 filename如果支持函数就可以 |
我这也是多个entry,每个entry自己指定目录,达不到他需求? |
@io3 恩。多个entry是可以达到的,但是output不行。我是希望output可以为每个文件指定不同的目录 |
输出的文件:
我不知道你们还有啥需求 |
额 好吧 看出来了 |
你好,请原谅在这里问一个关于webpack的问题。 需求是“单页应用&按需加载”。实践过程中遇到了问题。下面是问题描述: 目录结构: /app
|-- components/
| |-- header.js
|-- pages/
| |-- index.js
| |-- about.js
| |-- login.js
|-- entry.js 描述:
// 监听path路径,按需加载
window.addEventListener('popstate', function () {
switch (path) {
case '/':
require.ensure(['./pages/index.js']);
break;
case '/about':
require.ensure(['./pages/about.js']);
break;
}
}); 结果:生成了两个按需加载的chunks,但两个chunks都包含了 期望结果: webpack能实现我期望结果的需求吗? |
@epooren 可以在打包的时候将 {
entry: {
'components/header': 'components/header.js',
}
} 在html里需要手动引用。 |
@Mmzer 如果有这种需求, 建议去看下 FIS3. 这样的需求, 使用 FIS3 可以算是 so easy |
学习了 |
问题: 如何处理服务端模板的img中的src指向? 我看楼上已经有提过类似的, 也有回答, 不过只限于纯前端模板解析, 类似用loader去过一遍html之类的, 并不能满足需求, 我这里处理的是服务端的模板, 而家不一定是html, 有可能是ejs, jade之类, 这种情况下img的src好尴尬啊... |
如果是html或者类html模板(ejs、jade),应该有相应的loader可以做。 |
@treri @chemdemo 关于chunk id的问题,使用manifest和NamedModulesPlugin,是可以解决的。 |
mark |
mark |
mark 上面目录可以看出,各项目的 gulp 文件和 webpack 配置文件都存放在根目录下,但是这样有一个问题就是 package.json 只能有一个,这么引起的问题就是,各个子系统的依赖都在一个 package.json 文件里,好像不太利用管理各自的依赖,怎么能很好的区分各自的依赖呢? |
@avilang 这种纯粹是物理路径的区分 算不上前端工程吧 为什么不拆开成多个project存放呢 |
精彩,看10遍 |
@treri 稳定 module ID 相关的坑: http://zhenyong.site/2016/10/27/webpack-long-term-hash/ 对于 webpack 1.x 输出稳定的 module id 有多种方案,比较便捷的一种方式: |
@zhenyong 赞, 有时间看一下. 目前在使用 fis3 |
mark |
看了开头,忍不住想要评论下,和我的构思几乎一模一样,知己 |
mark |
@chemdemo 想请教一个问题,在local server , dev环境中访问 router.get('/', () => { }) , 是怎样读取 html-webpack-plugin 生成在内存中的html文件的。 |
@SimpleZn dev环境,文件是存储在内存然后由webpack-dev-server进行serve,而node渲染模板又要求文件是落地的,所以目前使用到html-webpack-plugin想再使用server-render是不行的。 早有人提过这个问题,http://stackoverflow.com/questions/36269456/render-file-generated-by-html-webpack-plugin-whithin-router ,貌似还是无解。 |
@chemdemo 我在你的代码中看到,node server-render时是提供了一个home.html , 这个文件是一个载体,当访问其中的a.html 时,实际上是由webpack-dev-server进行serve的,是这样的吧? 我之前是想在dev环境直接通过route来访问html-webpack-plugin生成在内存中的html文件,后来我在html-webpack-plugin的issues中找到了答案: jantimon/html-webpack-plugin#145 var express = require('express');
var app = express();
var webpack = require('webpack');
var path = require('path');
var compiler = webpack(require('./webpack.config.js'));
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: '/'
}));
app.use('*', function (req, res, next) {
var filename = path.join(compiler.outputPath,'index.html');
compiler.outputFileSystem.readFile(filename, function(err, result){
if (err) {
return next(err);
}
res.set('content-type','text/html');
res.send(result);
res.end();
});
});
app.listen(3000); |
Sit onlookers |
确实让我更加明晰了一些概念 但是有的还是需要写demo来测试一下 |
我有问题 |
@chemdemo 请看使用 fis3 + typescript 做的 vue-hackernews-2.0 https://github.com/Treri/fis3-typescript-vue-hackernews-2.0 fis3-hook-node_modules 还是挺好用的. |
请问博主一个问题,我现在有这么一个需求: //a.css @import "./reset.css"
@import "./common-ui.css" //enter.js require('a.css') //webpack.config.js enter:{
index:'enter.js'
},
output:{
filename:'[name].js',
}, 这样配置后,最终会生成一个index.css文件,index.css文件中包含了reset.css和common-ui.css。 请问,这样能实现么? |
评论太精彩,插个眼 |
本篇主要介绍webpack的基本原理以及基于webpack搭建前端项目工程化解决方案的思路。
下篇(还没写)探讨下对于Node.js作为后端的项目工程化、模块化、前后端共享代码、自动化部署的做法。
关于前端工程
下面是百科关于“软件工程”的名词解释:
其中,工程化是方法,是将软件研发的各个链路串接起来的工具。
对于软件“工程化”,个人以为至少应当有如下特点:
广泛意义上讲,前端也属于软件工程的范畴。
但前端没有Eclipse、Visual Studio等为特定语言量身打造的IDE。因为前端不需要编译,即改即生效,在开发和调试时足够方便,只需要打开个浏览器即可完成,所以前端一般不会扯到“工程”这个概念。
在很长一段时间里,前端很简单,比如下面简单的几行代码就能够成一个可运行前端应用:
但随着webapp的复杂程度不断在增加,前端也在变得很庞大和复杂,按照传统的开发方式会让前端失控:代码庞大难以维护、性能优化难做、开发成本变高。
感谢Node.js,使得JavaScript这门前端的主力语言突破了浏览器环境的限制可以独立运行在OS之上,这让JavaScript拥有了文件IO、网络IO的能力,前端可以根据需要任意定制研发辅助工具。
一时间出现了以Grunt、Gulp为代表的一批前端构建工具,“前端工程”这个概念逐渐被强调和重视。但是由于前端的复杂性和特殊性,前端工程化一直很难做,构建工具有太多局限性。
诚如 张云龙@fouber 所言:
html、css和js的配合才能保证webapp的运行,增量安装是按需加载的需要。开发完成后输出三种以上不同格式的静态资源,静态资源之间有可能存在互相依赖关系,最终构成一个复杂的资源依赖树(甚至网)。
所以,前端工程,最起码需要解决以下问题:
其中,资源管理是前端最需要也是最难做的一个环节。
注:个人以为,与前端工程化对应的另一个重要的领域是前端组件化,前者属于工具,解决研发效率问题,后者属于前端生态,解决代码复用的问题,本篇对于后者不做深入。
在此以开发一个多页面型webapp为例,给出上面所提出的问题的解决方案。
前端开发环境搭建
主要目录结构
这是个经典的前端项目目录结构,项目目结构在一定程度上约定了开发规范。业务开发的同学只需关注
src
目录即可,开发时尽可能最小化模块粒度,这是异步加载的需要。assets
是整个工程的产出,无需关注里边的内容是什么,至于怎么打包和解决资源依赖的,往下看。本地开发环境
我们使用开源web框架搭建一个webserver,便于本地开发和调试,以及灵活地处理前端路由,以
koa
为例,主要代码如下:运行
node app
启动本地server,浏览器输入http://localhost:8080/a.html
即可看到页面内容,最基本的环境就算搭建完成。如果只是处理静态资源请求,可以有很多的替代方案,如Fiddler替换文件、本地起Nginx服务器等等。搭建一个Web服务器,个性化地定制开发环境用于提升开发效率,如处理动态请求、dnsproxy(多用于解决移动端配置host的问题)等,总之local webserver拥有无限的可能。
定制动态请求
我们的local server是
localhost
域,在ajax请求时为了突破前端同源策略的限制,本地server需支持代理其他域下的api的功能,即proxy。同时还要支持对未完成的api进行mock的功能。webpack资源管理
资源的获取
ECMAScript 6之前,前端的模块化一直没有统一的标准,仅前端包管理系统就有好几个。所以任何一个库实现的loader都不得不去兼容基于多种模块化标准开发的模块。
webpack同时提供了对CommonJS、AMD和ES6模块化标准的支持,对于非前三种标准开发的模块,webpack提供了shimming modules的功能。
受Node.js的影响,越来越多的前端开发者开始采用CommonJS作为模块开发标准,
npm
已经逐渐成为前端模块的托管平台,这大大降低了前后端模块复用的难度。在webpack配置项里,可以把node_modules路径添加到resolve search root列表里边,这样就可以直接load npm模块了:
资源引用
根据webpack的设计理念,所有资源都是“模块”,webpack内部实现了一套资源加载机制,这与Requirejs、Sea.js、Browserify等实现有所不同,除了借助插件体系加载不同类型的资源文件之外,webpack还对输出结果提供了非常精细的控制能力,开发者只需要根据需要调整参数即可:
简单解释下上面的代码,
test
项表示匹配的资源类型,loader
或loaders
项表示用来加载这种类型的资源的loader,loader的使用可以参考using loaders,更多的loader可以参考list of loaders。对于开发者来说,使用loader很简单,最好先配置好特定类型的资源对应的loaders,在业务代码直接使用webpack提供的
require(source path)
接口即可:注意,
require()
还支持在资源path前面指定loader,即require(![loaders list]![source path])
形式:require()
时指定的loader会覆盖配置文件里对应的loader配置项。资源依赖处理
通过loader机制,可以不需要做额外的转换即可加载浏览器不直接支持的资源类型,如
.scss
、.less
、.json
、.ejs
等。但是对于css、js和图片,采用webpack加载和直接采用标签引用加载,有何不同呢?
运行webpack的打包命令,可以得到
a.js
的输出的结果:从输出结果可以看到,webpack内部实现了一个全局的
webpackJsonp()
用于加载处理后的资源,并且webpack把资源进行重新编号,每一个资源成为一个模块,对应一个id,后边是模块的内部实现,而这些操作都是webpack内部处理的,使用者无需关心内部细节甚至输出结果。上面的输出代码,因篇幅限制删除了其他模块的内部实现细节,完整的输出请看a.out.js,来看看图片的输出:
注意到图片资源的loader配置:
意思是,图片资源在加载时先压缩,然后当内容size小于~10KB时,会自动转成base64的方式内嵌进去,这样可以减少一个HTTP的请求。当图片大于10KB时,则会在
img/
下生成压缩后的图片,命名是[hash:8].[name].[ext]
的形式。hash:8
的意思是取图片内容hashsum值的前8位,这样做能够保证引用的是图片资源的最新修改版本,保证浏览器端能够即时更新。对于css文件,默认情况下webpack会把css content内嵌到js里边,运行时会使用
style
标签内联。如果希望将css使用link
标签引入,可以使用ExtractTextPlugin
插件进行提取。资源的编译输出
webpack的三个概念:模块(module)、入口文件(entry)、分块(chunk)。
其中,module指各种资源文件,如js、css、图片、svg、scss、less等等,一切资源皆被当做模块。
webpack编译输出的文件包括以下2种:
下面是一段entry和output项的配置示例:
其中
entry
项是入口文件路径映射表,output
项是对输出文件路径和名称的配置,占位符如[id]
、[chunkhash]
、[name]
等分别代表编译后的模块id、chunk的hashnum值、chunk名等,可以任意组合决定最终输出的资源格式。hashnum的做法,基本上弱化了版本号的概念,版本迭代的时候chunk是否更新只取决于chnuk的内容是否发生变化。细心的同学可能会有疑问,entry表示入口文件,需要手动指定,那么chunk到底是什么,chunk是怎么生成的?
在开发webapp时,总会有一些功能是使用过程中才会用到的,出于性能优化的需要,对于这部分资源我们希望做成异步加载,所以这部分的代码一般不用打包到入口文件里边。
对于这一点,webpack提供了非常好的支持,即code splitting,即使用
require.ensure()
作为代码分割的标识。例如某个需求场景,根据url参数,加载不同的两个UI组件,示例代码如下:
url分别输入不同的参数后得到瀑布图:
webpack将
require.ensure()
包裹的部分单独打包了,即图中看到的[hash].chunk.js
,既解决了异步加载的问题,又保证了加载到的是最新的chunk的内容。假设app还有一个入口页面
b.html
,那麽就需要相应的再增加一个入口文件b.js
,直接在entry
项配置即可。多个入口文件之间可能公用一个模块,可以使用CommonsChunkPlugin
插件对指定的chunks进行公共模块的提取,下面代码示例演示提取所有入口文件公用的模块,将其独立打包:资源的实时更新
引用模块,webpack提供了
require()
API(也可以通过添加bable插件来支持ES6的import
语法)。但是在开发阶段不可能改一次编译一次,webpack提供了强大的热更新支持,即HMR(hot module replace)。HMR简单说就是webpack启动一个本地webserver(webpack-dev-server),负责处理由webpack生成的静态资源请求。注意webpack-dev-server是把所有资源存储在内存的,所以你会发现在本地没有生成对应的chunk访问却正常。
下面这张来自webpack官网的图片,可以很清晰地说明
module
、entry
、chunk
三者的关系以及webpack如何实现热更新的:enter0表示入口文件,chunk1~4分别是提取公共模块所生成的资源块,当模块4和9发生改变时,因为模块4被打包在chunk1中,模块9打包在chunk3中,所以HMR runtime会将变更部分同步到chunk1和chunk3中对应的模块,从而达到hot replace。
webpack-dev-server的启动很简单,配置完成之后可以通过cli启动,然后在页面引入入口文件时添加webpack-dev-server的host即可将HMR集成到已有服务器:
因为我们的local server就是基于Node.js的webserver,这里可以更进一步,将webpack开发服务器以中间件的形式集成到local webserver,不需要cli方式启动(少开一个cmd tab):
启动HMR之后,每次保存都会重新编译生成新的chnuk,通过控制台的log,可以很直观地看到这一过程:
公用代码的处理:封装组件
webpack解决了资源依赖的问题,这使得封装组件变得很容易,例如:
使用:
正如开头所说,将三种语言、多种资源合并成js来管理,大大降低了维护成本。
对于新开发的组件或library,建议推送到
npm
仓库进行共享。如果需要支持其他加载方式(如RequireJS或标签直接引入),可以参考webpack提供的externals项。资源路径切换
由于入口文件是手动使用script引入的,在webpack编译之后入口文件的名称和路径一般会改变,即开发环境和生产环境引用的路径不同:
webpack提供了
HtmlWebpackPlugin
插件来解决这个问题,HtmlWebpackPlugin支持从模板生成html文件,生成的html里边可以正确解决js打包之后的路径、文件名问题,配置示例:这里资源根路径的配置在
output
项:其他入口html文件采用类似处理方式。
辅助工具集成
local server解决本地开发环境的问题,webpack解决开发和生产环境资源依赖管理的问题。在项目开发中,可能会有许多额外的任务需要完成,比如对于使用compass生成sprites的项目,因目前webpack还不直接支持sprites,所以还需要compass watch,再比如工程的远程部署等,所以需要使用一些构建工具或者脚本的配合,打通研发的链路。
因为每个团队在部署代码、单元测试、自动化测试、发布等方面做法都不同,前端需要遵循公司的标准进行自动化的整合,这部分不深入了。
对比&综述
前端工程化的建设,早期的做法是使用Grunt、Gulp等构建工具。但本质上它们只是一个任务调度器,将功能独立的任务拆解出来,按需组合运行任务。如果要完成前端工程化,这两者配置门槛很高,每一个任务都需要开发者自行使用插件解决,而且对于资源的依赖管理能力太弱。
在国内,百度出品的fis也是一种不错的工程化工具的选择,fis内部也解决了资源依赖管理的问题。因笔者没有在项目中实践过fis,所以不进行更多的评价。
webpack以一种非常优雅的方式解决了前端资源依赖管理的问题,它在内部已经集成了许多资源依赖处理的细节,但是对于使用者而言只需要做少量的配置,再结合构建工具,很容易搭建一套前端工程解决方案。
基于webpack的前端自动化工具,可以自由组合各种开源技术栈(Koa/Express/其他web框架、webpack、Sass/Less/Stylus、Gulp/Grunt等),没有复杂的资源依赖配置,工程结构也相对简单和灵活。
附上笔者根据本篇的理论所完成的一个前端自动化解决方案项目模板:
webpack-bootstrap
(完)。
The text was updated successfully, but these errors were encountered: