【经验分享】如何让ts项目支持装饰器语法,以及自动化发布相关的一些东西

本文所有内容都针对 LayaAir IDE 2.8.0 Mac 版,若您使用的是其他版本的 IDE,本文所述内容可能不太适合您。
 
1、如何让 layabox ts 项目支持装饰器语法
由于目前 typescript 装饰器语法还处于实验阶段,将来有可能更改,ts官方并不推荐,所以 layabox 也没有支持。
要启用装饰器语法,首先要配置 tsconfig.json, 让其支持装饰器语法:
{
"compilerOptions": {
...其他配置,
"experimentalDecorators": true, //启用装饰器语法
},
}

但修改完配置编译的时候会报错:
This syntax requires an imported helper but module 'tslib' cannot be found

开发群里有大神提供了一种解决办法:
右键 LayaAirIDE.app->显示包内容,定位到 Contents->Resources->app->node_modules->rollup->dist->rollup.js
修改 rollup.js 文件:将该文件的 16065 行注释掉,注释后的代码如下:
            error(err, pos) {
if (typeof err === 'string')
err = { message: err };
if (pos)
augmentCodeLocation(err, pos, curSource, id);
err.id = id;
err.hook = 'transform';
// return pluginContext.error(err);
},
该方法虽然可以解决 tslib 找不到的问题,并且编译后输出的文件可以正常运行,但是也屏蔽了其他错误,如果代码中有其他错误,最终输出的文件可能无法正确运行。
 
以下提供一个完美解决此问题的方案,该方案能顺带解决在低版本浏览器中支持 promise、await、async的问题。
在您的系统中已经安装了 Node.js 的前提下,只需要做两件事:
1)、Terminal 进入项目根目录,安装 tslib
npm install tslib

2)、修改项目的 tsconfig.json 配置文件为如下代码(核心是指定tslib的位置,因此 baseUrl 和 paths 必须配置):
{
"compilerOptions": {
"module": "es6",
"target": "es6",
"noEmitHelpers": true,
"sourceMap": true, //为了支持使用 vscode 断点调试,需要生成sourceMap
"experimentalDecorators": true, //启用装饰器语法
"baseUrl": ".", //必需,否则paths配置不会生效
"paths": {
"tslib": [
"tslib/tslib.d.ts"
]
},
"lib": [
"esnext", //在低版本浏览器上支持 async、await
"dom", //dom 支持
"es2015.promise" //在低版本浏览器上支持 promise
]
},
"exclude": [
"node_modules"
]
}

2、如何在 vscode 中断点调试
1)、参考官方文档《VSCode高效开发工作流配置指南》,安装 Debugger for Chrome 插件并创建 vscode 调试配置文件
2)、开启 sourceMap:
编辑 .laya/compile.js 文件,将第66-71行
		return bundle.write({
file: workSpaceDir + '/bin/js/bundle.js',
format: 'iife',
name: 'laya',
sourcemap: false
});
修改为
		return bundle.write({
file: workSpaceDir + '/bin/js/bundle.js',
format: 'iife',
name: 'laya',
sourcemap: true
});
编辑 tsconfig.json 将 "sourceMap": false 改为 "sourceMap": true
{
"compilerOptions": {
...其他配置,
"sourceMap": true,
}
由于开启了 sourceMap,发布时需要把生成的 bundle.js.map 排除,具体做法是在发布的时候勾选发布排除文件,修改筛选条件为:
!$basePath/js/**/*.js.map
或者可以在对应平台的发布配置文件(平台名.json)中修改:
{
...其他配置,
"exclude": true,
"excludeFilter": [
...其他筛选条件,
"!$basePath/js/**/*.js.map"
],
}

 
3、关于自动化编译、发布相关的一些东西
layabox官方已经提供了 layaair2-cmd 帮助我们实现命令行打包,但由于需要全局安装 layaair2-cmd 及 gulp,并且该命令行工具在 ide 升级了编译脚本之后可能无法正常使用,因此本文提供了一种与项目捆绑的自动化编译、发布方案。
此方案根据 layaair2-cmd 的代码以及 IDE 编译脚本引申而来。 
 
1)、搭建自动化编译/发布所需的环境
  a)、查看 .laya 目录下的编译脚本(*.js文件),查找所有类似 require(ideModuleDir+"xxxx") 的语句,列出 IDE 编译时用到的所有依赖包:
del
gulp
gulp-babel
gulp-image
gulp-jsonminify
gulp-rev
gulp-rev-collector
gulp-rev-delete-original
gulp-uglify-es
iconv-lite
request
require-dir
rollup
rollup-plugin-glsl
rollup-plugin-typescript2

  b)、右键 LayaAirIDE.app->显示包内容,定位到 Contents->Resources->app->node_modules,在该目录中查看上一步中找到的依赖包的版本,比如 del 这个包,在 node_modules 目录下找到 del 目录,其目录下有一个 package.json ,查看该文件的 version 字段即可得到 IDE 使用的del版本为 3.0.0
这里列出IDE编译时使用的所有js代码包的版本:
del: 3.0.0
gulp: 3.9.1
gulp-babel: 8.0.0
gulp-image: 4.4.0
gulp-jsonminify: 1.1.0
gulp-rev: 9.0.0
gulp-rev-collector: 1.3.1
gulp-rev-delete-original: 0.2.3
gulp-uglify-es: 1.0.4
iconv-lite: 0.4.19
request: 2.85.0
require-dir: 1.2.0
rollup: 1.16.6
rollup-plugin-glsl: 1.3.0
rollup-plugin-typescript2: 0.21.2
  c)、安装上述依赖包到本地
先执行 
npm init
初始化项目,生成 package.json 文件
然后依次执行 npm install --save-dev 代码包名称@版本,如:
npm install --save-dev del@3.0.0
安装前两步中找到的代码包
由于 gulp-babel 依赖于 babel,因此还需要安装 babel,我们查看 layaair2-cmd 的 package.json 文件,直接安装其引用的 babel 版本即可:
npm install --save-dev @babel/core@7.0.0
由于不明原因,发布时启用图片压缩功能时,压缩图片会报错
Warning: Command failed: .../node_modules/zopflipng-bin/vendor/zopflipng -y --lossy_8bit --lossy_transparent ...
Optimizing ...
Encoding error 82: color conversion to palette requested while a color isn't in palette
Decoding error 82: color conversion to palette requested while a color isn't in palette
报错的地方是 zopflipng-bin,我们直接从 LayaAirIDE.app->Contents->Resources->app->node_modules 目录中,找到 zopflipng-bin 文件夹,拷贝到项目的 node_modules 目录下替换掉原来的  zopflipng-bin 即可。
 
安装commander,用于解析命令行参数
npm install --save-dev commander@2.19.0

至此,编译所需的环境已经搭建完成,下面编写自动化编译、发布脚本。
 
2)、编写自动化编译、发布脚本(脚本基本照抄自 layaair2-cmd,只修改了一些脚本路径的计算)
在项目根目录下创建目录 dev-tools
  a)、创建并编写脚本 tools-compile.js
#!/usr/bin/env node
const program = require('commander');
const { fork } = require('child_process');
const path = require('path');

program.parse(process.argv);

const projPath = path.join(__dirname, '../');
const layaCompileDir = path.join(projPath, '.laya');
const gulpFilePath = path.join(layaCompileDir, 'compile.js');
const gulpPath = path.join(projPath, 'node_modules', 'gulp/bin/gulp.js');
const cmd = [`--gulpfile=${gulpFilePath}`, `--cwd=${projPath}`, 'compile'];

const _gulp = fork(gulpPath, cmd, {
silent: true,
shell: true,
});

_gulp.stdout.on('data', (data) => {
console.log(`${data}`);
});

_gulp.stderr.on('data', (data) => {
console.log(`${data}`);
});

_gulp.on('close', (code) => {
console.log(`exit:${code}`);
});

  b)、创建并编写脚本 tools-publish.js
#!/usr/bin/env node
const program = require('commander');
const { fork } = require('child_process');
const path = require('path');

// 这里的平台名称需要与 .laya/publish.js 脚本支持的平台名称对应,所以所有平台名都来自 .laya/publish.js 中 tasks 对象的 key
program
.option(
'-c, --config <configPlatform>',
'设置发布平台名称[web|wxgame|bdgame|xmgame|qqgame|oppogame|vivogame|Alipaygame|biligame|bytedancegame|hwgame|taobaominiapp],默认为web'
)
.parse(process.argv);

const jsonName = program.config ? `${program.config}.json` : 'web.json';

const projPath = path.join(__dirname, '../');
const layaCompileDir = path.join(projPath, '.laya');
const gulpFilePath = path.join(layaCompileDir, 'publish.js');
const gulpPath = path.join(projPath, 'node_modules', 'gulp/bin/gulp.js');
const cmd = [`--gulpfile=${gulpFilePath}`, `--config=${jsonName}`, `--cwd=${projPath}`, 'publish'];

const _gulp = fork(gulpPath, cmd, {
silent: true,
});

_gulp.stdout.on('data', (data) => {
console.log(`${data}`);
});

_gulp.stderr.on('data', (data) => {
console.log(`${data}`);
});

_gulp.on('close', (code) => {
console.log(`exit:${code}`);
});
  
c)、创建并编写脚本 tools-dev.js
#!/usr/bin/env node
const program = require('commander');
const path = require('path');

program.parse(process.argv);

const projPath = path.join(__dirname, '../');
const nodeModulesDir = path.join(projPath, 'node_modules');
const rollup = require(path.join(nodeModulesDir, 'rollup'));
const typescript = require(path.join(nodeModulesDir, 'rollup-plugin-typescript2')); // typescript2 plugin
const glsl = require(path.join(nodeModulesDir, 'rollup-plugin-glsl'));

let cache;

const config = {
input: projPath + '/src/Main.ts',
output: {
file: projPath + '/bin/js/bundle.js',
format: 'iife',
name: 'laya',
sourcemap: true,
},
cache: cache,
watch: {
include: 'src/**',
exclude: 'node_modules/**',
},
onwarn: (waring, warn) => {
if (waring.code == 'CIRCULAR_DEPENDENCY') {
console.log('warnning Circular dependency:');
console.log(waring);
}
},
treeshake: false,
plugins: [
typescript({
tsconfig: projPath + '/tsconfig.json',
check: true, // Set to false to avoid doing any diagnostic checks on the code
tsconfigOverride: { compilerOptions: { removeComments: true } },
include: /.*.ts/,
}),
glsl({
include: /.*(.glsl|.vs|.fs)$/,
sourceMap: false,
compress: false,
}),
],
};

const watcher = rollup.watch(config);
console.log(`Running 'rollup watcher'...`);

let tS;
watcher.on('event', (event) => {
// event.code 会是下面其中一个:
// START — 监听器正在启动(重启)
// BUNDLE_START — 构建单个文件束
// BUNDLE_END — 完成文件束构建
// END — 完成所有文件束构建
// ERROR — 构建时遇到错误
// FATAL — 遇到无可修复的错误
switch (event.code) {
case 'START':
tS = Date.now();
console.log(`Starting 'rebuild'...`);
break;
case 'END':
console.log(`Finished 'rebuild' after ${((Date.now() - tS) / 1000).toFixed(2)} s`);
break;
case 'ERROR':
case 'FATAL':
for (const k of Object.keys(event.error)) {
delete event.error[k];
}
console.log(event.error);
break;
case 'BUNDLE_END':
config.cache = event.result.cache;
break;
case 'BUNDLE_START':
default:
break;
}
});
  d)、创建并编写脚本 tools.js
#!/usr/bin/env node
const program = require('commander');
program
.usage('[command] [args]')
.command('compile', '编译项目.')
.command('publish', '发布项目.')
.command('dev', '开发模式,监控文件变化,自动编译')
.parse(process.argv);

e)、修改 .laya/compile.js 及 .laya/publish.js 
将以上两个脚本中的如下代码
function useOtherNode(){
return useIDENode||useCMDNode;
}
修改为:
function useOtherNode(){
return true;
}

至此,自动化编译、发布脚本已全部完成。
 
由于目前 layaair2-cmd 的 ui 命令,只能在 windows 环境下生成图集,本人也没有找到 IDE 导出资源时调用的代码,因此,当资源、场景文件更新时,还需要用 ide 手动导出资源,再执行脚本编译/发布。希望有大佬帮忙解决这一问题。
 
以下是使用方法
编译:
node dev-tools/tools.js compile

发布(默认平台:web)
首次使用脚本发布到某平台前务必使用IDE发布到对应平台一次,目的是生成该平台的发布配置文件 .laya/平台名.json
node dev-tools/tools.js publish
 
发布(其他平台)
node dev-tools/tools.js publish -c 平台名
可以使用如下命令查看支持的平台名称
node dev-tools/tools.js publish --help

监控文件变化,自动编译
node dev-tools/tools.js dev

当然,您也可以写一个 shell 脚本,包装以上几条命令。
 
经测试,自动编译脚本与ide编译的结果文件,仅有一处不同。

WechatIMG65.png

 
如果您使用的是 LayaAirIDE 2.8.0,此处提供一个 zip,您只需要把这个 zip 解压到您的项目根目录下,覆盖对应文件,并执行 
sh proj-init.sh
即可实现上面的所有功能,并且,配合 vscode eslint 插件,可实现实时检查代码规范,并且保存时自动格式化代码。
 
 

此 zip 中的发布脚本 .laya/publish.js 增加了一个压缩 JS 时移除代码中的 console 的功能,如果您不需要此功能,将该文件第 467-475 行注释即可。
//	options = Object.assign({}, options, {
// output: {
// comments: false,
// },
// compress: {
// drop_debugger: true,
// drop_console: true,
// },
// });

 
 
 
 
已邀请:

rabbit

赞同来自:

谢谢分享

sheen

赞同来自:

谢谢分享!辛苦了

陆仁毅

赞同来自:

感谢分享!
修改完compile.js 和publish.js之后最好修改下文件第一行的版本好,否则在打开发布面板的时候会强制更新,老文件会被加上时间标识移到同级的old目录下。

要回复问题请先

商务合作
商务合作