Skip to content

Commit 34ef4ce

Browse files
committed
Update meta & prepare npm publish
0 parents  commit 34ef4ce

20 files changed

+7334
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist

.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
src
3+
index.ts

README.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Closure Tools Helper
2+
3+
This module contains closure tools compiled `jar`s and various helper function to make dependency management easier.
4+
5+
This module exports 4 functions:
6+
```js
7+
const closureTools = require('closure-tools-helper');
8+
9+
closureTools.compiler(...);
10+
closureTools.templates(...);
11+
closureTools.stylesheets(...);
12+
closuretools.extractTemplateMsg(...);
13+
```
14+
15+
## Closure Compiler api
16+
17+
The closure compiler api abstracts the module system of closure compiler to something based on entry points, much similar to rollup.
18+
19+
```js
20+
(await closureTools.compiler(baseCompilerFlags, sourceGlob, entryPoints)).src()
21+
.pipe(...)
22+
```
23+
Note the requirements of `await`, due to building compiler flag being an async operation.
24+
The plugin controls `--module` and `-js` compiler flags. All the other flags must be provided with `baseCompilerFlags` argument.
25+
26+
`sourceGlob` is a glob pattern that provides js sources to the compiler. All files referenced by entryPoints must be provided by the glob.
27+
28+
`entryPoints` is an array where each element follows the below interface.
29+
```ts
30+
interface ICompilerEntryPoint {
31+
/**
32+
* This is what is used in `goog.require()`.
33+
* `null` means there is no entry module, but it can have some extraSources.
34+
*/
35+
id:string|null,
36+
/**
37+
* module name, used in specifying dependencies, and also used in output file name.
38+
*/
39+
name:string,
40+
/**
41+
* Array of module names that this bundle depends on.
42+
*/
43+
deps:string[],
44+
/**
45+
* Any files that are not reachable via `goog.require`s but still need to be provided
46+
* to the compiler.
47+
*/
48+
extraSources?:string[]
49+
}
50+
```
51+
52+
53+
## Closure Templates api
54+
55+
```js
56+
const compiler = require('closure-tools-helper');
57+
58+
gulp.task('build-soy', () => {
59+
return compiler.templates([.../* Command line args passed to SoyToJsSrcCompiler.jar */])
60+
.src()
61+
.pipe(gulp.dest('build'));
62+
});
63+
64+
gulp.task('extract-soy-messages', () => {
65+
return compiler.extractTemplateMsg([.../* Command line args passed to SoyMsgExtractor.jar */])
66+
.src();
67+
});
68+
```
69+
70+
Compiler apis returns a readable stream of the stdout. Users of the package should explicitly call `.src()` in order to turn the stream into flowing mode (same as `google-closure-compiler` gulp plugin).
71+
72+
### Runtime i18n
73+
74+
Closure templates by default supports compile-time i18n. Provide a second argument `TemplatesI18nOptions` to post-process js files generated by soy compiler to enable i18n. Its interface is what follows:
75+
```ts
76+
interface ITemplatesI18nOptions {
77+
/**
78+
* This is a string to replace `goog.getMsg` with.
79+
*/
80+
googGetMsg:string
81+
/**
82+
* This is a string that will be appended right after the `goog.module(..)` expression.
83+
*/
84+
header?:string
85+
/**
86+
* This must match files generated by the compiler jar.
87+
*/
88+
inputGlob:string|string[]
89+
outputPath?:string // If provided, files will be written to `outputPath/fileName`.
90+
}
91+
```
92+
93+
If runtime i18n is enabled, it will additionally transform legacy-style `goog.provide` modules into [`goog.module`](https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide), so that a consumer can import the compiled templates by using `const templates = goog.require('namespace.to.the.template')`, or `import templates from 'goog:namespace.to.the.template'` if the consumer is using [tsickle](https://github.com/angular/tsickle).
94+
95+
## SoyUtils
96+
97+
Import or reference `./third-party/soyutils.js` or `./third-party/soyutils_usegoog.js`.
98+
99+
## Closure Stylesheets api
100+
101+
```js
102+
const compiler = require('closure-tools-helper');
103+
104+
gulp.task('build-gss', () => {
105+
return compiler.stylesheets([.../* Command line args passed to closure-stylesheets.jar */])
106+
.src()
107+
})
108+
```
109+
110+
Compiler apis returns a readable stream of the stdout. User of the package should explicitly call `.src()` to turn the stream into a flowing mode.
111+
112+
## Installation
113+
114+
```
115+
yarn add https://github.com/seanl-adg/closure-tools-helper.git
116+
yarn add https://github.com/seanl-adg/closure-tools-helper.git#<specific-tag>
117+
```
118+
119+
## Build
120+
```
121+
tsc
122+
```

closure_library_deps.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "closure-tools-helper",
3+
"description": "Provides utility functionalities to call closure tools in nodejs.",
4+
"keywords": [
5+
"closure-library",
6+
"closure-compiler",
7+
"closure-stylesheets",
8+
"closure-templates"
9+
],
10+
"version": "1.0.0",
11+
"main": "dist/index.js",
12+
"types": "dist/index.d.ts",
13+
"license": "LGPL",
14+
"author": {
15+
"name": "theseanl",
16+
"email": "i73hi64d0wr5df8pckig@gmail.com"
17+
},
18+
"repository": {
19+
"type": "git",
20+
"url": "git+https://github.com/theseanl/closure-tools-helper.git"
21+
},
22+
"homepage": "https://github.com/theseanl/closure-tools-helper",
23+
"bugs": {
24+
"url": "https://github.com/theseanl/closure-tools-helper/issues"
25+
},
26+
"scripts": {
27+
"distill": "ts-node script/build_closure_deps.ts"
28+
},
29+
"dependencies": {
30+
"closure-library.ts": "^1.0.0",
31+
"esprima": "^4.0.0",
32+
"estraverse": "^4.2.0",
33+
"fancy-log": "^1.3.2",
34+
"fast-glob": "^2.1.0",
35+
"fs-extra": "^5.0.0",
36+
"google-closure-compiler": "^20180610.0.2",
37+
"google-closure-library": "20180506.0.0",
38+
"vinyl": "^2.1.0"
39+
},
40+
"devDependencies": {
41+
"@types/node": "^9.4.6",
42+
"@types/vinyl": "^2.0.2",
43+
"ts-node": "^7.0.0",
44+
"typescript": "^2.9.2"
45+
}
46+
}

script/build_closure_deps.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import DepsSorter from '../src/DepsSorter';
2+
import log = require('fancy-log');
3+
4+
async function main() {
5+
try {
6+
await DepsSorter.distillClosureDeps();
7+
} catch(e) {
8+
log.error(e);
9+
process.exit(1);
10+
}
11+
process.exit(0);
12+
}
13+
14+
main();

soyutils.d.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Type declarations to call soyutils functions from TS to be closure compiled.
3+
*
4+
* Currently this only contains limited declarations that are being used.
5+
* @todo Use clutz https://github.com/angular/clutz to generate `.d.ts` from
6+
* soyutils_usegoog.js.
7+
*/
8+
9+
declare module 'goog:soydata.VERY_UNSAFE' {
10+
namespace soydata.VERY_UNSAFE {
11+
export function ordainSanitizedHtml(str:string):any
12+
export function ordainSanitizedJs(str:string):any
13+
export function ordainSanitizedJsStrChars(str:string):any
14+
export function ordainSanitizedUri(str:string):any
15+
export function ordainSanitizedHtmlAttribute(str:string):any
16+
export function ordainSanitizedCss(str:string):any
17+
}
18+
export default soydata.VERY_UNSAFE;
19+
}

src/CompilerStream.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* @fileoverview It spawn a java process with provided arguments, and pipes stdout to
3+
* a stream that can be used with gulp plugins.
4+
* This is based on gulp plugin of google-closure-compiler package.
5+
*/
6+
7+
import Vinyl = require('vinyl');
8+
import log = require('fancy-log');
9+
import { spawn } from 'child_process';
10+
import { Stream } from 'stream';
11+
12+
export type TPostCompilationHook = (stdout:string) => Promise<string>
13+
14+
export default class CompilerStream extends Stream.Transform {
15+
16+
constructor(
17+
private jarPath,
18+
private args:string[],
19+
private outPath:string = 'dummy',
20+
private postCompilationHook?:TPostCompilationHook
21+
) {
22+
super({ objectMode: true });
23+
}
24+
25+
_transform(file, enc, cb) {
26+
// ignore empty files
27+
if (file.isNull()) {
28+
cb();
29+
return;
30+
}
31+
this.emit('error', new Error(`Streaming not supported`));
32+
cb();
33+
}
34+
_flush(cb) {
35+
this.doCompilation()
36+
.then((file) => {
37+
if (file) { this.push(file); }
38+
cb();
39+
})
40+
.catch((err) => {
41+
log.error(`Unknown error from ${this.jarPath}\n${err.message}`);
42+
cb();
43+
});
44+
}
45+
46+
/**
47+
* This is to make api similar to google-closure-compiler
48+
*/
49+
src() {
50+
process.nextTick(() => {
51+
let stdInStream = new Stream.Readable({
52+
read: function() {
53+
return new Vinyl();
54+
}
55+
});
56+
stdInStream.pipe(this);
57+
stdInStream.push(null);
58+
});
59+
this.resume();
60+
return this;
61+
}
62+
63+
private async doCompilation():Promise<Vinyl> {
64+
const process = spawn('java', ['-jar', this.jarPath, ...this.args]);
65+
66+
let stdOutData = '';
67+
let stdErrData = '';
68+
69+
process.stdout.on('data', (data) => {
70+
stdOutData += data;
71+
});
72+
process.stderr.on('data', (data) => {
73+
stdErrData += data;
74+
});
75+
process.on('error', (err) => {
76+
this.emit('error', new Error(`Process spawn error. Is java in the path?\n${err.message}`));
77+
});
78+
process.stdin.on('error', (err) => {
79+
this.emit('error', new Error(`Error writing to stdin of the compiler.\n${err.message}`));
80+
});
81+
82+
const closed = new Promise((resolve) => {
83+
process.on('close', resolve);
84+
});
85+
const stdOutEnded = new Promise((resolve) => {
86+
process.stdout.on('end', () => { resolve() });
87+
});
88+
const stdErrEnded = new Promise((resolve) => {
89+
process.stderr.on('end', () => { resolve() });
90+
});
91+
92+
const [code] = await Promise.all([closed, stdOutEnded, stdErrEnded]);
93+
94+
if (stdErrData.trim().length > 0) {
95+
log.warn(stdErrData);
96+
}
97+
98+
if (code !== 0) {
99+
this.emit('error', new Error(`Compilation error from ${this.jarPath}`));
100+
return;
101+
}
102+
103+
stdOutData = stdOutData.trim();
104+
105+
if (typeof this.postCompilationHook === 'function') {
106+
stdOutData = await this.postCompilationHook(stdOutData);
107+
}
108+
109+
if (stdOutData.length > 0) {
110+
return new Vinyl({
111+
path: this.outPath,
112+
contents: new Buffer(stdOutData)
113+
});
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)