Coder Social home page Coder Social logo

edp-build's Introduction

edp-build

Build Status Dependencies Status

Package for edp build.

# 检查build之后的结果是否正常的一种方式
edp amd list biz.js | sort | uniq -c | sort -k 1 -n -r | awk '{if($1>1){print $0}}'

# 如果有输出,那么说明可能存在问题

edp-build's People

Contributors

chestnutchen avatar duanlixin avatar erik168 avatar firede avatar jinzhubaofu avatar junmer avatar justineo avatar leeight avatar otakustay avatar pengxing avatar pydebug avatar quyatong avatar treelite avatar zengjialuo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

edp-build's Issues

moduleCompiler的一个问题

@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
  • foobar合并到x.js
  • alicebob合并到y.js

如果使用以下代码加载:

require(['x', 'y'], callback);

则可能出现不幸的情况:

  1. x.js 不幸y.js更早下载完
  2. 浏览器开始执行x.js
  3. 执行define('foo')
  4. 发现foo需要alice
  5. loader并不知道alicey.js
  6. loader果断地去加载了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()是安全的

但不幸的是,这种方案仅限于未合并模块的场景下。如果使用以下策略来合并:

  • foobar合在一起为x.js
  • 各种alice合在一起为y.js
  • 使用<script>标签来引入x.jsy.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的错误

那么这也就导致了,可能当alicefactory执行时,foo.hello是不存在的,会产生错误

解决这个问题的最显而易见的方法,自然是alice里显式声明依赖,加上require('bar'),或者各个alice模块有一个共同的依赖项bob,那么在bob上显式声明依赖也是可以的

当然这样老是require一个明明只要执行一次就不再有用的扩展型模块真的很累,那么可以有这样的方案:

<script src="x.js"></script>
<script>
    require(
        ['bar'],
        function () {
            require(['y'], initSystem)
        }
    );
</script>

通过全局require强行指定顺序,但代价是x.jsy.js不能并行加载,对网络有非常大的负作用


其他人如果在合并模块时有啥坑也建议记录下

防止压缩某些函数名

有一些方法是依赖函数名才能得到正确的结果的(比如从类名猜测当前的环境,比如callSuper这样的方法),因此是否可以在build过程中禁止某些函数名被压缩

配置可以使用正则或列表,没想好怎么配

需要一个替换esl.js的<script>标签的处理器

原因是我们的QA环境不连通外网,因此不能直接写CDN上的esl.js地址,而必须使用本地的。但是在build之后上线时,希望可以用CDN的地址

我们可以在需要的<script>标签上加上data-role="esl"的标记,由处理器找到script[data-role="esl"]的元素并替换src属性,属性值从.edpproj/metadataloaderUrl获取

css compressor问题

看了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文件都没有正常压缩。

tpl-merge.js文件中生成AST

tpl-merge.js文件中为了在业务模块的Action文件中找到依赖的tpl模板文件路径,用到了生成AST来解析;
假如用正则表达式来解析,会有那些问题?

支持build参数,指定打包不同的配置文件

功能需求是这样的。往往开发、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')不变。

CssCompressor的配置问题

现在默认会把css中图片的相路径改成绝对路径,但是在后续的PathMapper阶段没有处理,导致最后output目录中的css无法加载图片。

edp.amd.getModuleFile 和 edp.amd.getModuleId 和 ModuleCompiler 配合工作的问题

今天发现的一个项目中的case,简化一下,是这样子的:

module.conf的内容

{
    "baseUrl": "./src",
    "paths": {
        "ui": "ui/js"
    }
}

对于src/ui/js/widgets/banner.js这个文件来说,因为有paths的存在,导致edp.amd.getModuleId通过文件路径计算模块Id的时候,返回了两个Id:ui/js/widgets/bannerui/widgets/banner

我理解这2个Id应该都是合法的才对吧?

如果这2个Id都是合法的,那么 ui/widgets/banner 貌似可以计算出正确的路径,但是 ui/js/widgets/banner 是算不出来的 :-(

现在问题来了,当 ModuleCompiler 拿到2个Id之后,它应该选择用哪一个作为入口来处理呢?

关于CSS预处理问题

目前遇到stylus版本升级后不能向前兼容的问题,edp-webserveredp-buildstylus的依赖版本不统一就混乱了...

建议后续将这些处理引擎都从edp中移除,由项目自身决定处理引擎并通过配置项参数形式传递给edp进行处理。

目前雷哥@firede正在进行stylus部分的处理

vs-biz-finance在build之后功能不正常

提示的错误信息是:

Uncaught Error: [MODULE_TIMEOUT]Hang( biz/plan/List, biz/plan/ListView, er/tpl!3c821310.tpl.html ) Miss( none ) esl.source.js:122
waitTimeoutNotice

edp-build 的ModuleCompiler模块问题

  1. 非amd模块的排除,exclude和files冲突,导致exclude不管用

  2. package编译,指定包名称的编译不会进行合并:
    {
    "baseUrl": "src",
    "packages": [
    {
    "name": "health",
    "location": "src/center",
    "main": "main"
    }
    ],

    "combine": {
    "health": 1
    }
    }

加一个对js插件加载的模块合并的功能

其实把TplMerge改成MergePluginResource就行了,本来就能指定一个plugin id的

后续准备把模板编译成jsonp来完成静态资源独立无cookie域名的部署,因此会先把所有tpl通过html2js变成js文件,然后就需要合并了

这类问题以后放这边还是放edp项目?

edp build 支持 map配置

edp build的时候要求支持map配置。

例如
map: {
'ub-dsp-convert-tool': {
'er': 'er3',
'tpl': 'esui-tpl'
}
}
所有ub-dsp-convert-tool下面的er模块要转到er3.

edp doctor好像也要支持这个map 配置。

edp-build tplMerge问题

项目执行edp project init后,默认生成的edp-build-config.js中return的是
{
'default': [...],
'release': [...]
}
image

配置了tplMerge后,template.js生成的路径是放在src文件夹下面,没有在asset文件夹下;
改成return [];形式后正常;
image
请问是什么原因?

删除build之后的重复文件

现在执行edp-build之后,存在很多重复性的文件,例如指定了combine之后,实际上很多文件就可以剔除了。

几个缺失的功能

第一次用edp build,和我这边一开始定的要求相比有一些些欠缺:

  1. 没有给文件加copyright的功能,这个我自己写了个AddCopyright的处理器,用一下看看稳定的就PR上来
  2. 没能根据MD5或其它的生成文件名,现在来看还不急,至少还能用Last-Modified缓存,但后续总归是要永久缓存的
  3. 不少系统有个DEBUG或者啥的开关,这个我们现在一没统一,二build工具没能处理掉,是个问题,现在是我自己写个ReplaceDebug处理器在搞
  4. 没有source map,现在把comporess options放出来了但依旧不知道怎么玩- -
  5. tpl怎么编译?似乎没办法合并再自动改require的配置来处理,真一个一个加载太碎了
  6. processor写起来有点难,我在项目中要加几个自己的处理器,但是require不到AbstractProcessor,必须先安装个edp-build(单独装edp还不行),挺悲剧的

有空讨论下怎么来搞搞这些?现在的build代码结构也有点奇怪,每个处理器自己在控制“只运行一次(用个啥PROP的再file.set一下,跑前判断一下),还控制拿到的文件是不是符合自己的extnames配置,这些都应该能抽象出来

简化一下 module.conf 中 combine 的配置逻辑

现在配置模块合并的工作,需要写不少的代码,例如我想配置depstartup两个模块的话,就需要写如下的代码:

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 要求的文件,我们在发布的阶段需要忽略掉这些文件。

添加对AMD Wrapper的支持

目前模块合并不支持Wrapper,比如etpl暂时无法进行合并

需要修改的地方较多,util/analyse-module.jsutil/compile-module.jsutil/generate-module-code.jsutil/replace-require-resource.js

改完了后会发起pull request,大家check下~

compile-modules.js采用读取源文件方式合并-导致前置处理器修改内容无效

在edp-build/lib/compile-modules中采用读取源文件方式合并依赖文件(如下代码片段);如果在moduleCompiler之前去修改依赖文件的内容(如开关功能,资源位置)等将不生效,回写文件风险太大;建议能够使用依赖文件的file.data作为合并内容

var code = compileModule(
                fs.readFileSync( depFile, 'UTF-8' ),
                depId,
                moduleConfig,

Add TplMerge Processor

@erik168 @otakustay

我理解最简单的方案就是把

require('tpl!./tpl/textForm.tpl.html');
...
...
require('tpl!./tpl/richForm.tpl.html');

用到的文件全部找出来,然后合并为一个就好了,然后修改一下引用的文件地址:

require('tpl!asset/tpl/all.html');

TplMerge处理bs-edu的时候存在问题

有的文件处理了,有的文件没有处理,很奇怪。

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。

require.config 输出错误

某些项目无法便利的在 index.html 统一设置 require.config,而是根据接入方的需要,自行添加,比如:

require.config({
    paths: {
         xxx: 'xxx'
    }
});

edp build 会输出成:

require.config({
    baseUrl: '',
    paths: {
         xxx: 'xxx'
    }
});

这样就改写了原来的配置

并发文件处理可提速50%左右

目前 processor.process 这个方法是串行执行的。

实践得出,如果改成并发执行的话,整体构建的时间性能可以提升50%左右。

不知道这样做是不是好? 会否有其它隐患,比如,考虑 process 方法中有网络请求的情况?

支持动态获取combine配置

现在的combile配置是必须写在module.conf中的,但是一个系统如果超过200个文件,会导致module.conf非常大,写在一个文件中的配置也不容易维护

因此,项目会有需求分模块、分业务地写多个配置,在build时期再把这些配置合并起来

个人建议,在edp-build-config.js中可以额外提供一个getCombineConfig方法,返回的对象即为合并的配置,如果没有此方法,保持原逻辑默认使用module.conf

inline processor 需求

在某些场景下(如移动端),需要 把一些 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>

新升级导致md5rename出错

原因是 md5-renamer类的构造函数的参数option含有key:start,构造函数中有:this.start = this.start || 0; 而升级之后的processor其实是start是被占用为启动方法的,processor就在这里关掉了。

跟end对应的应该是begin吧,将配置改为begin就OK了。

util/compile-module的问题

@leeight

在util/compile-module.js中,line 51左右

var code = compileModule(
    fs.readFileSync(depFile, 'UTF-8'),
    depId,
    moduleConfig,
    ...

这里是直接fs read的文件内容,也就是说,不能够前置的修改context中的fileInfo的data来达到修改内容的目的……

是不是应该修改为processContext获取,获取失败再fs read?

path-mapper processer 修改 module-config 时需要对空的 baseUrl 做兼容处理

我们在项目中遇到这样一个问题:
我们把 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 );
}

combine的exclude语法问题

exclude看上去是按glob来实现的,所以有一个问题:

er/*确实会排除所有er下的模块,但是不会排除一个就叫er的模块,而因为erpackage.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就完了

解决 less 1.5.1 处理 @import 的一个bug

问题简化版是这样子的:

/** 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被调用了两次,导致的问题

edp 0.7.0版本在mac下build时,不区分文件大小写

src目录下有两个文件:

  • a.js,内容:
define(function(require) {
    var b = require('./b');
    var B = require('./B');
});
  • b.js,内容:
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

指定依赖库的build配置

比如esui提供2种打包配置:all把所有内容合成一个文件,base只合并基础控件,2者均输出一个esui/dist模块

在业务系统中,希望可以简单地通过指定这个依赖包的打包模式,如在module.conf中:

{
    build: {
        packages: {
            esui: 'all' // 或'base'
        }
    }
}

具体配置位置放在哪可以讨论,因为似乎不是ModuleCompiler处理的问题,放在ModuleCompiler构造函数里也不合适

另外,从这个话题延伸,事实上每个依赖库都应该能自己指定一些prebuildpostbuild的钩子,有些库可能要实时生成代码之类的

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.