Skip to content

Commit 85d9707

Browse files
authored
feat(ember-cli): Ember cli classic (#185).
- Make ember-cli work with classic ember apps (non-module-unification). - Still works for non-Ember Glimmer apps. - Added BEM_UNIQUE output mode where BEM classes are guaranteed unique within the same process. - Analyzer.analyze method accepts working directory instead of constructor. Allows Broccoli to pass working directory at runtime without creating the analyzer instance. - Abstract stylesheet resolver from Broccoli plugin to support both Glimmer and Ember with minimal code forks. Depend on experimental fork of glimmer-analyzer for now. - Transport objects are now backed by a proper Class interface instead of a POJO. - Abstract the addon's env configuration with getEnv and getOptions methods. - Use sync fs methods in broccoli and ember-cli for better build perf. - Glimmer helpers re-named to sensible private values. - Build @css-blocks/glimmer to CJS *and* AMD so Ember can import the runtime. - Helper runtimes are automagically injected by ember-cli addon. - Abstract helper names out of runtime tests for maintainability. - Ember-CLI Template Discovery Test suite for addons, engines (eager and lazy), and apps (real and dummy). Lazy engines and some app features are failing and skipped. - Bump ember-cli and associated packages to 3.3.0. - Have Travis test with node versions 8 and 10. - Add global disable option. - Auto-discover common preprocessor extensions for Ember's `app.css` output file. - Add failing test case for node modules resolution use case.
1 parent dfd32ac commit 85d9707

File tree

129 files changed

+2608
-2744
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

129 files changed

+2608
-2744
lines changed

Diff for: .travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ language: node_js
66

77
node_js:
88
- "8"
9-
- "9"
9+
- "10"
1010

1111
env:
1212
- CXX=g++-4.8

Diff for: package.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"chai": "^3.5.0",
2828
"husky": "^0.14.3",
2929
"istanbul": "^0.4.5",
30-
"lerna": "^3.0.0-beta.1",
30+
"lerna": "^2.11.0",
3131
"loader-utils": "^1.1.0",
3232
"markdown-toc": "^1.2.0",
3333
"mocha": "^3.4.2",
@@ -36,7 +36,6 @@
3636
"mock-require": "^2.0.2",
3737
"outdent": "^0.4.1",
3838
"perfectionist": "^2.4.0",
39-
"postcss": "^6.0.21",
4039
"prettier": "^1.8.2",
4140
"remap-istanbul": "^0.9.5",
4241
"source-map-support": "^0.4.15",
@@ -45,7 +44,7 @@
4544
"tslint": "^5.9.1",
4645
"typedoc": "^0.11.0",
4746
"typedoc-plugin-monorepo": "^0.1.0",
48-
"typescript": "^2.8.1",
47+
"typescript": "~2.8.0",
4948
"watch": "^1.0.2"
5049
},
5150
"workspaces": [

Diff for: packages/@css-blocks/broccoli/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
"debug": "^3.1.0",
4646
"fs-extra": "^5.0.0",
4747
"opticss": "^0.3.0",
48-
"postcss": "^6.0.21",
4948
"recursive-readdir": "^2.2.2",
5049
"walk-sync": "^0.3.2"
5150
}

Diff for: packages/@css-blocks/broccoli/src/index.ts

+47-26
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,99 @@
11
import * as fs from "fs-extra";
22
import * as path from "path";
33

4-
import { Analyzer, BlockCompiler, StyleMapping } from "@css-blocks/core";
4+
import { Analyzer, Block, BlockCompiler, StyleMapping } from "@css-blocks/core";
55
import { TemplateTypes } from "@opticss/template-api";
6+
67
import * as debugGenerator from "debug";
7-
import { OptiCSSOptions, Optimizer } from "opticss";
8-
import * as postcss from "postcss";
8+
import { OptiCSSOptions, Optimizer, postcss } from "opticss";
99
import * as readdir from "recursive-readdir";
1010

1111
import { BroccoliPlugin } from "./utils";
1212

1313
const debug = debugGenerator("css-blocks:broccoli");
1414

15+
export interface Transport {
16+
id: string;
17+
mapping?: StyleMapping<keyof TemplateTypes>;
18+
blocks?: Set<Block>;
19+
analyzer?: Analyzer<keyof TemplateTypes>;
20+
css?: string;
21+
}
22+
1523
export interface BroccoliOptions {
1624
entry: string[];
1725
output: string;
26+
root: string;
1827
analyzer: Analyzer<keyof TemplateTypes>;
19-
transport: {[key: string]: object};
28+
transport: Transport;
2029
optimization?: Partial<OptiCSSOptions>;
2130
}
2231

2332
class BroccoliCSSBlocks extends BroccoliPlugin {
2433

2534
private analyzer: Analyzer<keyof TemplateTypes>;
26-
private entry: string[];
35+
private entries: string[];
2736
private output: string;
28-
private transport: { [key: string]: object };
37+
private root: string;
38+
private transport: Transport;
2939
private optimizationOptions: Partial<OptiCSSOptions>;
30-
3140
// tslint:disable-next-line:prefer-whatever-to-any
3241
constructor(inputNode: any, options: BroccoliOptions) {
3342
super([inputNode], { name: "broccoli-css-blocks" });
3443

35-
this.entry = options.entry;
36-
this.output = options.output;
37-
this.analyzer = options.analyzer;
44+
this.entries = options.entry.slice(0);
45+
this.output = options.output || "css-blocks.css";
3846
this.transport = options.transport;
3947
this.optimizationOptions = options.optimization || {};
40-
41-
if (!this.output) {
42-
throw new Error("CSS Blocks Broccoli Plugin requires an output file name.");
43-
}
48+
this.analyzer = options.analyzer;
49+
this.root = options.root || process.cwd();
50+
this.transport.css = this.transport.css ? this.transport.css : "";
4451
}
4552

4653
async build() {
4754
let options = this.analyzer.cssBlocksOptions;
4855
let blockCompiler = new BlockCompiler(postcss, options);
4956
let optimizer = new Optimizer(this.optimizationOptions, this.analyzer.optimizationOptions);
5057

58+
// When no entry points are passed, we treat *every* template as an entry point.
59+
let discover = !this.entries.length;
60+
5161
// This build step is *mostly* just a pass-through of all files!
5262
// QUESTION: Tom, is there a better way to do this in Broccoli?
5363
let files = await readdir(this.inputPaths[0]);
5464
for (let file of files) {
5565
file = path.relative(this.inputPaths[0], file);
56-
await fs.ensureDir(path.join(this.outputPath, path.dirname(file)));
66+
67+
// If we're in Classic or Pods mode, every hbs file is an entry point.
68+
if (discover && path.extname(file) === ".hbs") { this.entries.push(file); }
69+
70+
fs.ensureDirSync(path.join(this.outputPath, path.dirname(file)));
5771
try {
58-
await fs.symlink(
72+
fs.symlinkSync(
5973
path.join(this.inputPaths[0], file),
6074
path.join(this.outputPath, file),
6175
);
6276
} catch (e) {
6377
// tslint:disable-next-line:no-console
6478
console.log("Error linking", path.join(this.inputPaths[0], file), "to output directory.");
6579
}
80+
6681
}
6782

83+
// The glimmer-analyzer package tries to require() package.json
84+
// in the root of the directory it is passed. We pass it our broccoli
85+
// tree, so it needs to contain package.json too.
86+
// TODO: Ideally this is configurable in glimmer-analyzer. We can
87+
// contribute that option back to the project. However,
88+
// other template integrations may want this available too...
89+
fs.writeFileSync(
90+
path.join(this.outputPath, "package.json"),
91+
fs.readFileSync(path.join(this.root, "package.json")),
92+
);
93+
6894
// Oh hey look, we're analyzing.
6995
this.analyzer.reset();
70-
await this.analyzer.analyze(...this.entry);
96+
await this.analyzer.analyze(this.outputPath, this.entries);
7197

7298
// Compile all Blocks and add them as sources to the Optimizer.
7399
// TODO: handle a sourcemap from compiling the block file via a preprocessor.
@@ -80,10 +106,9 @@ class BroccoliCSSBlocks extends BroccoliPlugin {
80106
let filename = filesystemPath || options.importer.debugIdentifier(block.identifier, options);
81107

82108
// If this Block has a representation on disk, remove it from our output tree.
83-
// TODO: This isn't working right now because `importer.filesystemPath` doesn't return the expected path...
84109
if (filesystemPath) {
85-
debug(`Removing block file ${path.relative(options.rootDir, filesystemPath)} from output.`);
86-
await fs.unlink(path.join(this.outputPath, path.relative(options.rootDir, filesystemPath)));
110+
debug(`Removing block file ${filesystemPath} from output.`);
111+
fs.unlinkSync(filesystemPath);
87112
}
88113

89114
// Add the compiled Block file to the optimizer.
@@ -106,13 +131,9 @@ class BroccoliCSSBlocks extends BroccoliPlugin {
106131
this.transport.mapping = styleMapping;
107132
this.transport.blocks = blocks;
108133
this.transport.analyzer = this.analyzer;
109-
this.transport.css = optimized.output;
134+
this.transport.css += optimized.output.content.toString();
110135

111-
// Write our compiled CSS to the output tree.
112-
await fs.writeFile(
113-
path.join(this.outputPath, this.output),
114-
optimized.output.content.toString(),
115-
);
136+
debug(`Compilation Finished: ${this.transport.id}`);
116137

117138
}
118139

Diff for: packages/@css-blocks/broccoli/test/plugin-test.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as assert from "assert";
2-
import * as fs from "fs-extra";
3-
// import * as path from "path";
2+
import * as path from "path";
43

54
import { GlimmerAnalyzer } from "@css-blocks/glimmer";
65
import { TempDir, buildOutput, createTempDir } from "broccoli-test-helper";
@@ -50,8 +49,8 @@ describe("Broccoli Plugin Test", function () {
5049
},
5150
});
5251

53-
let transport = {};
54-
let analyzer = new GlimmerAnalyzer(input.path(), {
52+
let transport = { id: "test-transport" };
53+
let analyzer = new GlimmerAnalyzer({}, {}, {
5554
app: { name: "test" },
5655
types: {
5756
stylesheet: { definitiveCollection: "components" },
@@ -64,18 +63,18 @@ describe("Broccoli Plugin Test", function () {
6463

6564
let compiler = new BroccoliCSSBlocks(input.path(), {
6665
entry: [entryComponentName],
66+
root: path.join(__dirname, "../.."),
6767
output: "css-blocks.css",
6868
transport,
6969
analyzer,
7070
});
71-
let output = await buildOutput(compiler);
71+
await buildOutput(compiler);
7272

7373
assert.ok(Object.keys(transport).length, "Transport Object populated");
7474
assert.ok(transport["mapping"], "Mapping property is populated in Transport Object");
7575
assert.ok(transport["blocks"], "Blocks property is populated in Transport Object");
7676
assert.ok(transport["analyzer"], "Analyzer property is populated in Transport Object");
7777
assert.ok(transport["css"], "CSS property is populated in Transport Object");
78-
assert.equal(await fs.readFile(output.path("css-blocks.css"), "utf8"), transport["css"].content, "CSS File generated");
7978
});
8079
});
8180
});

Diff for: packages/@css-blocks/core/src/Analyzer/Analyzer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export abstract class Analyzer<K extends keyof TemplateTypes> {
5050
this.dynamicStyles = new MultiMap();
5151
}
5252

53-
abstract analyze(...entryPoints: string[]): Promise<Analyzer<K>>;
53+
abstract analyze(dir: string, entryPoints: string[]): Promise<Analyzer<K>>;
5454
abstract get optimizationOptions(): TemplateIntegrationOptions;
5555

5656
// TODO: We don't really want to burn the world here.

Diff for: packages/@css-blocks/core/src/BlockCompiler/ConflictResolver.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ export class ConflictResolver {
422422
}
423423

424424
// Wrap our list of CompoundSelectors in ParsedSelector containers and return.
425-
return mergedSelectors.map(sel => new ParsedSelector(sel));
425+
return mergedSelectors.map(sel => new ParsedSelector(sel, sel.toString()));
426426
}
427427
sourceLocation(block: Block, node: postcss.Node): SourceLocation | undefined {
428428
let blockPath = this.config.importer.debugIdentifier(block.identifier, this.config);

Diff for: packages/@css-blocks/core/src/BlockTree/AttrValue.ts

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ export class AttrValue extends Style<AttrValue, Block, Attribute, never> {
8989
switch (config.outputMode) {
9090
case OutputMode.BEM:
9191
return `${this.parent.cssClass(config)}${ this.isPresenceRule ? "" : `-${this.value}`}`;
92+
case OutputMode.BEM_UNIQUE:
93+
return `${this.parent.cssClass(config)}${ this.isPresenceRule ? "" : `-${this.value}`}`;
9294
default:
9395
return assertNever(config.outputMode);
9496
}

Diff for: packages/@css-blocks/core/src/BlockTree/Attribute.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,9 @@ export class Attribute extends Inheritable<Attribute, Block, BlockClass, AttrVal
169169
cssClass(config: ResolvedConfiguration) {
170170
switch (config.outputMode) {
171171
case OutputMode.BEM:
172-
let cssClassName = this.blockClass.cssClass(config);
173-
return `${cssClassName}--${this.token.name}`;
172+
return `${this.blockClass.cssClass(config)}--${this.token.name}`;
173+
case OutputMode.BEM_UNIQUE:
174+
return `${this.blockClass.cssClass(config)}--${this.token.name}`;
174175
default:
175176
return assertNever(config.outputMode);
176177
}

Diff for: packages/@css-blocks/core/src/BlockTree/Block.ts

+23
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import * as crypto from "crypto";
2+
13
import { MultiMap, ObjectDictionary } from "@opticss/util";
24
import { whatever } from "@opticss/util";
35
import {
@@ -20,6 +22,24 @@ import { BlockClass } from "./BlockClass";
2022
import { Inheritable } from "./Inheritable";
2123
import { Styles } from "./Styles";
2224

25+
// Random seed for `get_guid`. Consistent for life of process.
26+
const RAND_SEED = Math.floor(Math.random() * 1000);
27+
28+
/**
29+
* Generates a 5 digit, process-wide globally unique
30+
* identifier from a given input identifier. This
31+
* generated identifier hash will remain consistent
32+
* for the life of the process that spawned it.
33+
* @prop identifier string Input Block identifier.
34+
* @returns This Block's guid hash.
35+
*/
36+
function gen_guid(identifier: string): string {
37+
return crypto.createHash("md5")
38+
.update(identifier + RAND_SEED)
39+
.digest("hex")
40+
.slice(0, 5);
41+
}
42+
2343
export class Block
2444
extends Inheritable<Block, Block, never, BlockClass> {
2545

@@ -29,6 +49,8 @@ export class Block
2949
private _implements: Block[] = [];
3050
private hasHadNameReset = false;
3151

52+
public readonly guid: string;
53+
3254
/**
3355
* array of paths that this block depends on and, if changed, would
3456
* invalidate the compiled css of this block. This is usually only useful in
@@ -45,6 +67,7 @@ export class Block
4567
this._dependencies = new Set<string>();
4668
this.rootClass = new BlockClass(ROOT_CLASS, this);
4769
this.stylesheet = stylesheet;
70+
this.guid = gen_guid(identifier);
4871
this.addClass(this.rootClass);
4972
}
5073

Diff for: packages/@css-blocks/core/src/BlockTree/BlockClass.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,9 @@ export class BlockClass extends Style<BlockClass, Block, Block, Attribute> {
198198
public cssClass(config: ResolvedConfiguration): string {
199199
switch (config.outputMode) {
200200
case OutputMode.BEM:
201-
if (this.isRoot) {
202-
return `${this.block.name}`;
203-
} else {
204-
return `${this.block.name}__${this.name}`;
205-
}
201+
return this.isRoot ? `${this.block.name}` : `${this.block.name}__${this.name}`;
202+
case OutputMode.BEM_UNIQUE:
203+
return this.isRoot ? `${this.block.name}_${this.block.guid}` : `${this.block.name}_${this.block.guid}__${this.name}`;
206204
default:
207205
return assertNever(config.outputMode);
208206
}

Diff for: packages/@css-blocks/core/src/configuration/OutputMode.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
*/
44
export enum OutputMode {
55
BEM = "BEM",
6+
BEM_UNIQUE = "BEM_UNIQUE",
67
}

0 commit comments

Comments
 (0)