edp-build
Package for edp build.
# 检查build之后的结果是否正常的一种方式
edp amd list biz.js | sort | uniq -c | sort -k 1 -n -r | awk '{if($1>1){print $0}}'
# 如果有输出,那么说明可能存在问题
Package for edp build.
License: Other
@leeight @errorrik
起因是这样:我想给某个项目编译的模块结果都加上个别名:例如 init 等同于 phoenix/init
So... 我在module.conf的path中增加了这么一句:"phoenix": "../src"
事实上这句的效果要比 "phoenix":"."好很多
编译的结果基本满足了需要,单个文件可以得到这样的结果:
define('phoenix/init', function (require, exports, module) {
function init() {
}
return init;
});
/** d e f i n e */
define("init", function(require){ return require("phoenix/init"); });
但是,当我在配置combine的时候,编译的结果中,模块的定义变成了:
define('init', function (require, exports, module) {
function init() {
}
return init;
});
/** d e f i n e */
define("phoenix/init", function(require){ return require("init"); });
事实上单个模块的编译结果是我想要的,我期望真正的模块定义被定义给phoenix/init,而不是init,这样我在另一个项目中调用,模块是不会出现定义冲突而导致无效的状况。
扫了一下edp-build的源码,我用的是edp-build (1.0.7),发现这是因为在combine和单个模块定义时对模块的moduleId获取行为的不同导致的。
出于实验,我在util/compile-module.js的combineModuleCode方法中,修改depId的获取方式,使打包的编译结果符合了这种方式。
我看到当前版本已经1.0.14了,请问这个问题是否已经修复?
第一个坑:合并后的加载顺序顺序
假设以下场景:
foo
依赖alice
foo
和bar
合并到x.js
alice
和bob
合并到y.js
如果使用以下代码加载:
require(['x', 'y'], callback);
则可能出现不幸的情况:
x.js
不幸 比y.js
更早下载完x.js
define('foo')
foo
需要alice
alice
在y.js
中alice.js
在依赖比较复杂的场景下,这会直接造成y.js
和没有一样,完全没有合并的意义
解决这个坑的一个方法是,使用<script>
标签来引入脚本
<script src="y.js"></script>
<script src="x.js"></script>
由于浏览器的特性,x.js
肯定会先执行完,随后才有y.js
进行执行,因此不必担心顺序。代价是在低版本IE下会变成串行加载(可忽略),以及下面要说的另一个问题
第二个坑:扩展型模块的依赖
假设这样的场景:
foo
定义了一个对象bar
扩展了foo
模块,为foo
模块添加了一个hello()
方法alice
会用到foo.hello()
并且在开发过程中,有非常多的类似alice
的模块,因此不想每个模块中写一句require('bar')
,最后决定通过确定系统启动顺序来控制:
require(
['foo', 'bar'],
function () {
require(['alice'], initSystem);
}
);
以上的代码用于保证在任何类似alice
的模块之前,bar
已经完成了对foo
的扩展,因此调用foo.hello()
是安全的
但不幸的是,这种方案仅限于未合并模块的场景下。如果使用以下策略来合并:
foo
和bar
合在一起为x.js
alice
合在一起为y.js
<script>
标签来引入x.js
和y.js
alice
模块在factory
中直接使用foo.hello()
(非软性依赖)那么,在执行y.js
时会直接报错,原因是x.js
中的各个define
其实只是对模块进行了预定义(preDefine
,即只留一个占位说这个模块已经被loader记住了,但还没真正执行factory
),也就是说假设这样的代码:
<script src="x.js"></script>
<script>
require('foo');
</script>
是无法执行的,那句require('foo')
会抛出Module Missing
的错误
那么这也就导致了,可能当alice
的factory
执行时,foo.hello
是不存在的,会产生错误
解决这个问题的最显而易见的方法,自然是alice
里显式声明依赖,加上require('bar')
,或者各个alice
模块有一个共同的依赖项bob
,那么在bob
上显式声明依赖也是可以的
当然这样老是require
一个明明只要执行一次就不再有用的扩展型模块真的很累,那么可以有这样的方案:
<script src="x.js"></script>
<script>
require(
['bar'],
function () {
require(['y'], initSystem)
}
);
</script>
通过全局require
强行指定顺序,但代价是x.js
和y.js
不能并行加载,对网络有非常大的负作用
其他人如果在合并模块时有啥坑也建议记录下
有一些方法是依赖函数名才能得到正确的结果的(比如从类名猜测当前的环境,比如callSuper
这样的方法),因此是否可以在build过程中禁止某些函数名被压缩
配置可以使用正则或列表,没想好怎么配
原因是我们的QA环境不连通外网,因此不能直接写CDN上的esl.js
地址,而必须使用本地的。但是在build之后上线时,希望可以用CDN的地址
我们可以在需要的<script>
标签上加上data-role="esl"
的标记,由处理器找到script[data-role="esl"]
的元素并替换src
属性,属性值从.edpproj/metadata
的loaderUrl
获取
看了css compressor的代码,里面有这么一段配置:
function CssCompressor( options ) {
options = edp.util.mix( {
files: [ '*.css' ], // 这里的配置
compressOptions: {}
}, options );
AbstractProcessor.call( this, options );
}
配置的是所有css文件,然后在edp-build-config.js
里面的默认配置是这样的:
new CssCompressor()
这样build完后,所有css文件都没有压缩。。。
默认配置应该把*.less
也加上吧?我们这样修改edp-build-config.js
后就能正常压缩:
new CssCompressor({
files: ["*.less"],
compressOptions: {
keepBreaks: false
}
});
最近才发现,包括edpx-mobile
在内,在默认配置下,最终输出的css文件都没有正常压缩。
功能基本不可用,主要是因为pluginId
参数的问题
tpl-merge.js文件中为了在业务模块的Action文件中找到依赖的tpl模板文件路径,用到了生成AST来解析;
假如用正则表达式来解析,会有那些问题?
edp-build-config.js中定义了:
exports.exclude = [
'src/biz/**'
];
但是通过tpl merge之后在最终的output 中。src/biz/下的模板文件还在那里。
功能需求是这样的。往往开发、QA测试、线上环境接口配置是不一样的。每次手动切换非常麻烦而且上线时容易遗忘切换成环境。希望可以通过添加edp build的参数,打包使用不同的配置文件。例如:
// 将开发时配置文件打包
edp build --dev
// 将QA联调配置文件打包
edp build --qa
// 将生产环境配置文件打包
edp build --prod
解决方案大约是这样:
首先 假设在src下放置一个conf文件夹;其中按环境划分子文件夹
src/conf
src/conf/dev
src/conf/prod
src/conf/qa
其次 在require.config里对配置文件设定一个path;在源码中,是指定到conf/dev/config.js
require.config({
basrUrl: 'src',
paths: {
conf: 'conf/dev'
}
});
然后 在打包前,先将conf的path修改成参数指定的值,例如edp build --prod
require.config({
basrUrl: 'xxx',
paths: {
conf: 'conf/prod'
}
});
最后 正常继续其他打包工作
这样,在源码中使用require('config/some-config')
不变。
现在默认会把css中图片的相路径改成绝对路径,但是在后续的PathMapper阶段没有处理,导致最后output目录中的css无法加载图片。
以bs-edu
为例,执行了edp build -f
,然后执行edp ws start --document-root output
,发现会请求:http://leeight.baidu.com:8888/dep/esui/3.0.1/src/css/img/esui.png
这个是不合理的
今天发现的一个项目中的case,简化一下,是这样子的:
module.conf的内容
{
"baseUrl": "./src",
"paths": {
"ui": "ui/js"
}
}
对于src/ui/js/widgets/banner.js
这个文件来说,因为有paths
的存在,导致edp.amd.getModuleId
通过文件路径计算模块Id的时候,返回了两个Id:ui/js/widgets/banner
和 ui/widgets/banner
。
我理解这2个Id应该都是合法的才对吧?
如果这2个Id都是合法的,那么 ui/widgets/banner
貌似可以计算出正确的路径,但是 ui/js/widgets/banner
是算不出来的 :-(
现在问题来了,当 ModuleCompiler 拿到2个Id之后,它应该选择用哪一个作为入口来处理呢?
目前遇到stylus
版本升级后不能向前兼容的问题,edp-webserver
与edp-build
对stylus
的依赖版本不统一就混乱了...
建议后续将这些处理引擎都从edp
中移除,由项目自身决定处理引擎并通过配置项参数形式传递给edp
进行处理。
目前雷哥@firede正在进行stylus
部分的处理
提示的错误信息是:
Uncaught Error: [MODULE_TIMEOUT]Hang( biz/plan/List, biz/plan/ListView, er/tpl!3c821310.tpl.html ) Miss( none ) esl.source.js:122
waitTimeoutNotice
非amd模块的排除,exclude和files冲突,导致exclude不管用
package编译,指定包名称的编译不会进行合并:
{
"baseUrl": "src",
"packages": [
{
"name": "health",
"location": "src/center",
"main": "main"
}
],
"combine": {
"health": 1
}
}
其实把TplMerge
改成MergePluginResource
就行了,本来就能指定一个plugin id的
后续准备把模板编译成jsonp
来完成静态资源独立无cookie域名的部署,因此会先把所有tpl
通过html2js
变成js文件,然后就需要合并了
这类问题以后放这边还是放edp
项目?
https://github.com/ecomfe/esui/blob/master/src/extension/TableEdit.js#L678
这当然是文件本身的一个BUG,但要从语法上来说并没有错,但过不了ModuleCompiler
,这是设计如此吗?
outputDir
不存在和config
文件不存在时是不是exit
非零比较好呢?既然是error,为啥还要exit(0)
呢?
edp build的时候要求支持map配置。
例如
map: {
'ub-dsp-convert-tool': {
'er': 'er3',
'tpl': 'esui-tpl'
}
}
所有ub-dsp-convert-tool下面的er模块要转到er3.
edp doctor好像也要支持这个map 配置。
方便在完成build后干点事儿,例如比较md5给增加urlArgs之类的
RT
现在执行edp-build之后,存在很多重复性的文件,例如指定了combine之后,实际上很多文件就可以剔除了。
第一次用edp build
,和我这边一开始定的要求相比有一些些欠缺:
AddCopyright
的处理器,用一下看看稳定的就PR上来Last-Modified
缓存,但后续总归是要永久缓存的DEBUG
或者啥的开关,这个我们现在一没统一,二build工具没能处理掉,是个问题,现在是我自己写个ReplaceDebug
处理器在搞processor
写起来有点难,我在项目中要加几个自己的处理器,但是require
不到AbstractProcessor
,必须先安装个edp-build
(单独装edp
还不行),挺悲剧的有空讨论下怎么来搞搞这些?现在的build代码结构也有点奇怪,每个处理器自己在控制“只运行一次(用个啥PROP的再file.set
一下,跑前判断一下),还控制拿到的文件是不是符合自己的extnames
配置,这些都应该能抽象出来
现在配置模块合并的工作,需要写不少的代码,例如我想配置dep
和startup
两个模块的话,就需要写如下的代码:
var dep = [
base,
echarts,
negative(invalid) // 不合法的amd,需要去掉
];
var startup = [
bizCommon,
negative(base), // common/dep里面已经包含base的代码了,这里就不需要了
negative(echarts), // common/dep里面已经包含base的代码了,这里就不需要了
negative(invalid) // 不合法的amd,需要去掉
];
var combine = {
'common/dep': {
modules: compact(dep)
},
'startup': {
modules: compact(startup)
}
};
return combine;
这里面比较纠结的事情是一直在重复negative
相关的代码(因为前面的模块合并了,其它的模块就不要再合并),为了
简化这个事情,我打算这么做,支持.v2
这个配置参数来识别是新的格式(因为模块名不太可能是.v2
开头吧)
{
"combine": {
".v2": true,
"exclude": [
"~er", "xxx", "foo", "bar/**"
],
"modules": [
{
"ria": [
"~er", "~esui"
]
},
{
"startup": [
"~foo",
"~bar"
]
}
]
}
}
因为modules
是一个数组,所以可以按照顺序来处理。合并完毕ria
之后,ria
包含的模块Id默认就变成了startup
的exclude
配置,这样子来简化上面negative
的重复写法。
另外新增了一个exclude
这个是针对所有modules
里面配置的模块生效的,目的是简化negative(invalid)
的写法。系统中
总会存在一些不太符合 amd 要求的文件,我们在发布的阶段需要忽略掉这些文件。
目前模块合并不支持Wrapper
,比如etpl
暂时无法进行合并
需要修改的地方较多,util/analyse-module.js
,util/compile-module.js
,util/generate-module-code.js
,util/replace-require-resource.js
改完了后会发起pull request
,大家check下~
// 依赖关系
{
"foo": ["io/File", "net/Http", "er/View"],
"io/File": [],
"net/Http": [],
"er/View": ["net/Http"]
}
最终的输出顺序应该是:
1. io/File
2. net/Http
3. er/View
4. foo
现在的情况是:
1. er/View
2. net/Http
3. io/File
4. foo
在edp-build/lib/compile-modules中采用读取源文件方式合并依赖文件(如下代码片段);如果在moduleCompiler之前去修改依赖文件的内容(如开关功能,资源位置)等将不生效,回写文件风险太大;建议能够使用依赖文件的file.data作为合并内容
var code = compileModule(
fs.readFileSync( depFile, 'UTF-8' ),
depId,
moduleConfig,
具体在util/analyse-module.js
中,需要添加对NewExpression
的检测,而非仅是CallExpression
先记录下,稍后修复
我理解最简单的方案就是把
require('tpl!./tpl/textForm.tpl.html');
...
...
require('tpl!./tpl/richForm.tpl.html');
用到的文件全部找出来,然后合并为一个就好了,然后修改一下引用的文件地址:
require('tpl!asset/tpl/all.html');
有的文件处理了,有的文件没有处理,很奇怪。
output/asset/account/index/info/View.js:2: require('er/tpl!../../../../fed9624e.tpl.html');
output/asset/account/otherConfig/OtherAccConfView.js:2: require('er/tpl!../../../fed9624e.tpl.html');
output/asset/account/main.js:851: require('er/tpl!./tpl.html');
output/asset/account/main.js:1812: require('er/tpl!./tpl.html');
output/asset/account/main.js:1982: require('er/tpl!./tpl.html');
output/asset/account/main.js:2363: require('er/tpl!./entry.html');
output/asset/account/tree/AccTreeView.js:2: require('er/tpl!../../../fed9624e.tpl.html');
output/asset/common/entry/EntryView.js:2: require('er/tpl!../../../fed9624e.tpl.html');
output/asset/common/regionLayer/View.js:2: require('er/tpl!../../../fed9624e.tpl.html');
output/asset/common/sidebar/SidebarView.js:2: require('er/tpl!../../../fed9624e.tpl.html');
另外,计算最终模板路径的逻辑也需要再确认一下是否OK。
期望被rename的文件可以指定输出文件名,例如:{origName}-{md5}.{extName}
某些项目无法便利的在 index.html 统一设置 require.config,而是根据接入方的需要,自行添加,比如:
require.config({
paths: {
xxx: 'xxx'
}
});
edp build 会输出成:
require.config({
baseUrl: '',
paths: {
xxx: 'xxx'
}
});
这样就改写了原来的配置
代码build之后,影响了require的逻辑,导致callback不触发。
https://svn.baidu.com/app/ecom/baike/trunk/web/src/main/webapp
临时注释了ModuleCompiler
貌似问题不出现了
目前 processor.process
这个方法是串行执行的。
实践得出,如果改成并发执行的话,整体构建的时间性能可以提升50%左右。
不知道这样做是不是好? 会否有其它隐患,比如,考虑 process 方法中有网络请求的情况?
现在的combile
配置是必须写在module.conf
中的,但是一个系统如果超过200个文件,会导致module.conf
非常大,写在一个文件中的配置也不容易维护
因此,项目会有需求分模块、分业务地写多个配置,在build时期再把这些配置合并起来
个人建议,在edp-build-config.js
中可以额外提供一个getCombineConfig
方法,返回的对象即为合并的配置,如果没有此方法,保持原逻辑默认使用module.conf
在某些场景下(如移动端),需要 把一些 js
or css
放到 <head>
里
所以期望有个 processor
可以做以下 工作
edp-build-config.js
var inlinePorcessor = new InlinePorcessor({
extnames: 'html,tpl'
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script>
{edp-inline:'src/common/meta.js'}
</script>
<style>
{edp-inline:'src/common/head.css'}
</style>
</head>
<body>
hello
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script>
/* meta.js content */
</script>
<style>
/* head.css content */
</style>
</head>
<body>
hello
</body>
</html>
原因是 md5-renamer类的构造函数的参数option含有key:start,构造函数中有:this.start = this.start || 0; 而升级之后的processor其实是start是被占用为启动方法的,processor就在这里关掉了。
跟end对应的应该是begin吧,将配置改为begin就OK了。
在util/compile-module.js中,line 51左右
var code = compileModule(
fs.readFileSync(depFile, 'UTF-8'),
depId,
moduleConfig,
...
这里是直接fs read的文件内容,也就是说,不能够前置的修改context中的fileInfo的data来达到修改内容的目的……
是不是应该修改为processContext获取,获取失败再fs read?
参见这行:https://github.com/ecomfe/edp-build/blob/master/lib/processor/module-compiler.js#L65
这里用的是rawData
,我在ModuleCompiler
前还有一个processor,将某些文件进行了些改动,自动添加一些代码,然后经过ModuleCompiler
后这些代码就又消失了
这个当时是有什么考虑吗?
html-minifier
主要用与 去掉 生产环境 html
中的 注释<!-- -->
并且保留 etpl
的注释
我用 https://github.com/kangax/html-minifier 搞了一个
请看 是否是通用需求,要加不?
我们在项目中遇到这样一个问题:
我们把 esl 的 baseUrl 配置在父模板parent.tpl
中,在子模板中会定义一些模块的 urlArgs。
parent.tpl:
require.config({
baseUrl: '/src'
});
子模板:
require.config({
urlArgs: {
'main': 'v=20150127'
}
});
经过path-mapper
处理器后子模板变成了
require.config({
urlArgs: {
'main': 'v=20150127'
},
baseUrl: ''
});
所以需要在处理 path-mapper 时对 baseUrl
作兼容处理。
if ( confData.baseUrl ) {
confData.baseUrl = valueReplacer( confData.baseUrl );
}
exclude
看上去是按glob来实现的,所以有一个问题:
er/*
确实会排除所有er
下的模块,但是不会排除一个就叫er
的模块,而因为er
的package.json
里指定了main
,所以er/main
就是er
,导致这个模块会被打包进来
当然如果从文件系统的角度理解,er/*
就是 ER下的模块但不包含ER 没错,但是从模块的角度来看,er/*
很明显应该是 ER这个package的模块全都不要 的意思,这里是否有必要重新处理?
所以现在的配置大概是这样的:
'startup/ria': {
include: getModulesFromPackage('ef')
.concat(getModulesFromPackage('ria'))
.concat(getModulesFromPackage('er-track')),
exclude: [
'mini-event',
'mini-event/**',
'underscore',
'underscore/**',
'etpl',
'etpl/**',
'er',
'er/**',
'esui'
'esui/**'
]
}
不得不说……略重复。当然我可以写er**
,但是如果有个库叫erxxx
就完了
问题简化版是这样子的:
/** dep/esui/3.1.0-alpha.6/src/css/SearchBox.less */
.normal-search-icon() {
background: transparent url(../img/search.png) no-repeat center -25px;
}
.active-search-icon() {
background: transparent url(../img/search.png) no-repeat center 0;
}
.skin-search-button {
.normal-search-icon();
&:active {
.active-search-icon();
}
&:hover {
.active-search-icon();
}
}
然后dep/esui/3.1.0-alpha.6/src/css/main.less
引用了SearchBox.less
,例如:
/** dep/esui/3.1.0-alpha.6/src/css/main.less*/
@import "./SearchBox.less";
最后bug.less
引用了了main.less
,也就是这样子的:
/** bug.less*/
@import "dep/esui/3.1.0-alpha.6/src/css/main.less";
此时执行lessc -ru bug.less
,产出的代码是有问题的,如果直接执行lessc -ru dep/esui/3.1.0-alpha.6/src/css/main.less
则是OK的。
目测问题是因为.active-search-icon();
这个mixin
被调用了两次,导致的问题
一些非amd js 如 html5shiv , respond 在构建时需要合并
是否考虑,做一个file-concat
的 Processor
,
类似 grunt-contrib-concat
建议先参考一下fis,如果有现成的就拿来用吧。
src
目录下有两个文件:
define(function(require) {
var b = require('./b');
var B = require('./B');
});
define(function(require) {
console.log('test');
});
build完,a的输出:
define("B",function(){console.log("test")}),define("b",function(){console.log("test")}),define("a",function(require){require("./B"),require("./b")});
居然define了一个B
模块,并且文件内容跟b.js内容一样。
hushicai@48e49d19b824f843e02f544db8e1e4cdd72ff96d
比如esui
提供2种打包配置:all
把所有内容合成一个文件,base
只合并基础控件,2者均输出一个esui/dist
模块
在业务系统中,希望可以简单地通过指定这个依赖包的打包模式,如在module.conf
中:
{
build: {
packages: {
esui: 'all' // 或'base'
}
}
}
具体配置位置放在哪可以讨论,因为似乎不是ModuleCompiler
处理的问题,放在ModuleCompiler
构造函数里也不合适
另外,从这个话题延伸,事实上每个依赖库都应该能自己指定一些prebuild
或postbuild
的钩子,有些库可能要实时生成代码之类的
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.