Skip to content

Commit 86df276

Browse files
Remove and Restore Hashbangs (#320)
* Restore hashbangs * Missing import * Tests for using banner option with Rollup
1 parent c443664 commit 86df276

21 files changed

+269
-10
lines changed

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ import {
3232
} from './transformers/source/transforms';
3333
import { preCompilation, create as createChunkTransforms } from './transformers/chunk/transforms';
3434
import { Mangle } from './transformers/mangle';
35+
import { Ebbinghaus } from './transformers/ebbinghaus';
3536
import { SourceTransform } from './transform';
3637

3738
export default function closureCompiler(requestedCompileOptions: CompileOptions = {}): Plugin {
3839
const mangler: Mangle = new Mangle();
40+
const memory: Ebbinghaus = new Ebbinghaus();
3941
let inputOptions: InputOptions;
4042
let context: PluginContext;
4143
let sourceTransforms: Array<SourceTransform>;
@@ -49,6 +51,7 @@ export default function closureCompiler(requestedCompileOptions: CompileOptions
4951
context,
5052
requestedCompileOptions,
5153
mangler,
54+
memory,
5255
inputOptions,
5356
{},
5457
);
@@ -76,6 +79,7 @@ export default function closureCompiler(requestedCompileOptions: CompileOptions
7679
context,
7780
requestedCompileOptions,
7881
mangler,
82+
memory,
7983
inputOptions,
8084
outputOptions,
8185
);

src/transform.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { logTransformChain } from './debug';
1818
import { TransformInterface, PluginOptions } from './types';
1919
import { PluginContext, InputOptions, OutputOptions, TransformSourceDescription } from 'rollup';
2020
import { Mangle } from './transformers/mangle';
21+
import { Ebbinghaus } from './transformers/ebbinghaus';
2122
import * as path from 'path';
2223
import MagicString from 'magic-string';
2324
import { DecodedSourceMap as RemappingDecodedSourceMap } from '@ampproject/remapping/dist/types/types';
@@ -27,6 +28,7 @@ class Transform implements TransformInterface {
2728
protected context: PluginContext;
2829
protected pluginOptions: PluginOptions;
2930
protected mangler: Mangle;
31+
protected memory: Ebbinghaus;
3032
protected inputOptions: InputOptions;
3133
protected outputOptions: OutputOptions;
3234
public name: string = 'Transform';
@@ -35,12 +37,14 @@ class Transform implements TransformInterface {
3537
context: PluginContext,
3638
pluginOptions: PluginOptions,
3739
mangler: Mangle,
40+
memory: Ebbinghaus,
3841
inputOptions: InputOptions,
3942
outputOptions: OutputOptions,
4043
) {
4144
this.context = context;
4245
this.pluginOptions = pluginOptions;
4346
this.mangler = mangler;
47+
this.memory = memory;
4448
this.inputOptions = inputOptions;
4549
this.outputOptions = outputOptions;
4650
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Copyright 2020 The AMP HTML Authors. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS-IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { ChunkTransform } from '../../transform';
18+
import { TransformInterface } from '../../types';
19+
import MagicString from 'magic-string';
20+
21+
/**
22+
* Closure Compiler will not compile code that is prefixed with a hashbang (common to rollup output for CLIs).
23+
*
24+
* This transform will restore the hashbang if Ebbinghaus knows it exists.
25+
*/
26+
export default class HashbangApplyTransform extends ChunkTransform implements TransformInterface {
27+
public name = 'HashbangApplyTransform';
28+
29+
/**
30+
* @param source MagicString of source to process post Closure Compilation.
31+
*/
32+
public async post(source: MagicString): Promise<MagicString> {
33+
if (this.memory.hashbang === null) {
34+
return source;
35+
}
36+
37+
source.prepend(this.memory.hashbang + '\n');
38+
return source;
39+
}
40+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Copyright 2020 The AMP HTML Authors. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS-IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { ChunkTransform } from '../../transform';
18+
import { TransformInterface } from '../../types';
19+
import MagicString from 'magic-string';
20+
21+
/**
22+
* Closure Compiler will not compile code that is prefixed with a hashbang (common to rollup output for CLIs).
23+
*
24+
* This transform will remove the hashbang (if present) and ask Ebbinghaus to remember if for after compilation.
25+
*/
26+
export default class HashbangRemoveTransform extends ChunkTransform implements TransformInterface {
27+
public name = 'HashbangRemoveTransform';
28+
29+
/**
30+
* @param source MagicString of source to process post Closure Compilation.
31+
*/
32+
public async pre(source: MagicString): Promise<MagicString> {
33+
const stringified = source.trim().toString();
34+
const match = /^#!.*/.exec(stringified);
35+
36+
if (!match) {
37+
return source;
38+
}
39+
40+
this.memory.hashbang = match[0];
41+
source.remove(0, match[0].length);
42+
return source;
43+
}
44+
}

src/transformers/chunk/transforms.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
RenderedChunk,
2222
TransformSourceDescription,
2323
} from 'rollup';
24+
import HashbangRemoveTransform from './hashbang-remove';
25+
import HashbangApplyTransform from './hashbang-apply';
2426
import IifeTransform from './iife';
2527
import CJSTransform from './cjs';
2628
import LiteralComputedKeys from './literal-computed-keys';
@@ -30,24 +32,30 @@ import StrictTransform from './strict';
3032
import ConstTransform from './const';
3133
import { ChunkTransform, chunkLifecycle } from '../../transform';
3234
import { Mangle } from '../mangle';
35+
import { Ebbinghaus } from '../ebbinghaus';
3336
import { CompileOptions } from 'google-closure-compiler';
3437
import { pluckPluginOptions } from '../../options';
3538

3639
const TRANSFORMS: Array<typeof ChunkTransform> = [
40+
HashbangRemoveTransform,
41+
// Acorn can parse content starting here
3742
ConstTransform,
3843
IifeTransform,
3944
CJSTransform,
4045
LiteralComputedKeys,
4146
StrictTransform,
4247
ExportTransform,
4348
ImportTransform,
49+
// Acorn cannot parse content starting here.
50+
HashbangApplyTransform,
4451
];
4552

4653
/**
4754
* Instantiate transform class instances for the plugin invocation.
4855
* @param context Plugin context to bind for each transform instance.
4956
* @param requestedCompileOptions Originally requested compile options from configuration.
5057
* @param mangler Mangle instance used for this transform instance.
58+
* @param memory Ebbinghaus instance used to store information that could be lost from source.
5159
* @param inputOptions Rollup input options
5260
* @param outputOptions Rollup output options
5361
* @return Instantiated transform class instances for the given entry point.
@@ -56,12 +64,14 @@ export function create(
5664
context: PluginContext,
5765
requestedCompileOptions: CompileOptions,
5866
mangler: Mangle,
67+
memory: Ebbinghaus,
5968
inputOptions: InputOptions,
6069
outputOptions: OutputOptions,
6170
): Array<ChunkTransform> {
6271
const pluginOptions = pluckPluginOptions(requestedCompileOptions);
6372
return TRANSFORMS.map(
64-
transform => new transform(context, pluginOptions, mangler, inputOptions, outputOptions),
73+
transform =>
74+
new transform(context, pluginOptions, mangler, memory, inputOptions, outputOptions),
6575
);
6676
}
6777

src/transformers/ebbinghaus.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Copyright 2020 The AMP HTML Authors. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS-IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Hermann Ebbinghaus is credited with discovering the forgetting curve.
19+
*
20+
* This class stores data the compiler would typically loose to the forgetting curve.
21+
* For instance:
22+
* - original source contained a `hashbang`
23+
* - original source used external imports
24+
*
25+
* This data can be used later to inform transforms following Closure Compiler.
26+
*
27+
* For more information, visit: https://en.wikipedia.org/wiki/Hermann_Ebbinghaus
28+
*/
29+
30+
export class Ebbinghaus {
31+
public hashbang: string | null = null;
32+
}

src/transformers/source/hashbang.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Copyright 2020 The AMP HTML Authors. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS-IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { SourceTransform } from '../../transform';
18+
import { TransformInterface } from '../../types';
19+
import MagicString from 'magic-string';
20+
21+
/**
22+
* Closure Compiler will not compile code that is prefixed with a hashbang (common to rollup output for CLIs).
23+
*
24+
* This transform will remove the hashbang (if present) and ask Ebbinghaus to remember if for after compilation.
25+
*/
26+
export default class HashbangTransform extends SourceTransform implements TransformInterface {
27+
public name = 'HashbangTransform';
28+
29+
public transform = async (id: string, source: MagicString): Promise<MagicString> => {
30+
const stringified = source.trim().toString();
31+
const match = /^#!.*/.exec(stringified);
32+
33+
if (!match) {
34+
return source;
35+
}
36+
37+
this.memory.hashbang = match[0];
38+
source.remove(0, match[0].length);
39+
return source;
40+
};
41+
}

src/transformers/source/transforms.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,22 @@
1515
*/
1616

1717
import { SourceTransform, sourceLifecycle } from '../../transform';
18-
// import { ImportTransform } from './imports';
19-
// import { ExportTransform } from './exports';
2018
import { Mangle } from '../mangle';
2119
import { PluginContext, InputOptions, OutputOptions, TransformSourceDescription } from 'rollup';
2220
import { CompileOptions } from 'google-closure-compiler';
21+
import HashbangTransform from './hashbang';
22+
import { Ebbinghaus } from '../ebbinghaus';
2323

24-
const TRANSFORMS: Array<typeof SourceTransform> = [];
25-
// Temporarily disabling SourceTransforms, aligning for future release.
24+
const TRANSFORMS: Array<typeof SourceTransform> = [HashbangTransform];
25+
// Temporarily disabling many SourceTransforms, aligning for future release.
2626
// ImportTransform, ExportTransform
2727

2828
/**
2929
* Instantiate transform class instances for the plugin invocation.
3030
* @param context Plugin context to bind for each transform instance.
3131
* @param requestedCompileOptions Originally requested compile options from configuration.
3232
* @param mangler Mangle instance used for this transform instance.
33+
* @param memory Ebbinghaus instance used to store information that could be lost from source.
3334
* @param inputOptions Rollup input options
3435
* @param outputOptions Rollup output options
3536
* @return Instantiated transform class instances for the given entry point.
@@ -38,10 +39,13 @@ export const create = (
3839
context: PluginContext,
3940
requestedCompileOptions: CompileOptions,
4041
mangler: Mangle,
42+
memory: Ebbinghaus,
4143
inputOptions: InputOptions,
4244
outputOptions: OutputOptions,
4345
): Array<SourceTransform> =>
44-
TRANSFORMS.map(transform => new transform(context, {}, mangler, inputOptions, outputOptions));
46+
TRANSFORMS.map(
47+
transform => new transform(context, {}, mangler, memory, inputOptions, outputOptions),
48+
);
4549

4650
/**
4751
* Run each transform's `transform` lifecycle.

test/generator.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const fixtureLocation = (category, name, format, optionsKey, minified = false) =
5656
: `${name}.js`
5757
}`;
5858

59-
async function compile(category, name, codeSplit, closureFlags, optionKey, format, wrapper) {
59+
async function compile(category, name, codeSplit, closureFlags, optionKey, format, wrapper, banner) {
6060
const bundle = await rollup.rollup({
6161
input: fixtureLocation(category, name, format, optionKey, false),
6262
plugins: [compiler(closureFlags[optionKey])],
@@ -69,6 +69,7 @@ async function compile(category, name, codeSplit, closureFlags, optionKey, forma
6969
format,
7070
name: wrapper,
7171
sourcemap: true,
72+
banner,
7273
});
7374

7475
const output = [];
@@ -107,7 +108,7 @@ async function compile(category, name, codeSplit, closureFlags, optionKey, forma
107108
return output;
108109
}
109110

110-
function generate(shouldFail, category, name, codeSplit, formats, closureFlags, wrapper) {
111+
function generate(shouldFail, category, name, codeSplit, formats, closureFlags, wrapper, banner) {
111112
const targetLength = longest(formats);
112113
const optionLength = longest(Object.keys(closureFlags));
113114

@@ -125,6 +126,7 @@ function generate(shouldFail, category, name, codeSplit, formats, closureFlags,
125126
optionKey,
126127
format,
127128
wrapper,
129+
banner,
128130
);
129131

130132
t.plan(output.length);
@@ -144,8 +146,9 @@ function failureGenerator(
144146
formats = [ESM_OUTPUT],
145147
closureFlags = defaultClosureFlags,
146148
wrapper = null,
149+
banner = null,
147150
) {
148-
generate(true, category, name, codeSplit, formats, closureFlags, wrapper);
151+
generate(true, category, name, codeSplit, formats, closureFlags, wrapper, banner);
149152
}
150153

151154
function generator(
@@ -155,8 +158,9 @@ function generator(
155158
formats = [ESM_OUTPUT],
156159
closureFlags = defaultClosureFlags,
157160
wrapper = null,
161+
banner = null,
158162
) {
159-
generate(false, category, name, codeSplit, formats, closureFlags, wrapper);
163+
generate(false, category, name, codeSplit, formats, closureFlags, wrapper, banner);
160164
}
161165

162166
module.exports = {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env node
2+
export function foo(){return"hello world"};

0 commit comments

Comments
 (0)