Skip to content

Commit 865267c

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 1f2e558 commit 865267c

File tree

141 files changed

+3532
-2219
lines changed

Some content is hidden

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

141 files changed

+3532
-2219
lines changed

.travis.yml

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

77
node_js:
88
- "8"
9+
<<<<<<< HEAD
910
- "9"
11+
=======
12+
>>>>>>> test: Have Travis use node versions 8 and 10.
1013
- "10"
1114

1215
env:

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": [

packages/@css-blocks/broccoli/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@
2121
"lintfix": "tslint -t msbuild --project . -c tslint.cli.json --fix",
2222
"coverage": "istanbul cover -i dist/src/**/*.js --dir ./build/coverage node_modules/mocha/bin/_mocha -- dist/test --opts test/mocha.opts",
2323
"remap": "remap-istanbul -i build/coverage/coverage.json -o coverage -t html",
24-
"watch": "watch 'yarn run test' src test types-local --wait=1"
24+
"watch": "watch 'yarn run test' src test --wait=1"
2525
},
2626
"publishConfig": {
2727
"access": "public"
2828
},
2929
"devDependencies": {
3030
"@css-blocks/code-style": "^0.18.0",
31-
"@css-blocks/glimmer": "^0.19.0"
31+
"@css-blocks/glimmer": "^0.19.0",
32+
"watch": "^1.0.2"
3233
},
3334
"dependencies": {
3435
"@css-blocks/core": "^0.19.0",
@@ -44,7 +45,6 @@
4445
"debug": "^3.1.0",
4546
"fs-extra": "^5.0.0",
4647
"opticss": "^0.3.0",
47-
"postcss": "^6.0.21",
4848
"recursive-readdir": "^2.2.2",
4949
"walk-sync": "^0.3.2"
5050
}

packages/@css-blocks/broccoli/src/index.ts

+51-25
Original file line numberDiff line numberDiff line change
@@ -1,69 +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-
import { OptiCSSOptions, Optimizer } from "opticss";
7-
import * as postcss from "postcss";
6+
7+
import * as debugGenerator from "debug";
8+
import { OptiCSSOptions, Optimizer, postcss } from "opticss";
89
import * as readdir from "recursive-readdir";
910

1011
import { BroccoliPlugin } from "./utils";
1112

13+
const debug = debugGenerator("css-blocks:broccoli");
14+
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+
1223
export interface BroccoliOptions {
1324
entry: string[];
1425
output: string;
26+
root: string;
1527
analyzer: Analyzer<keyof TemplateTypes>;
16-
transport: {[key: string]: object};
28+
transport: Transport;
1729
optimization?: Partial<OptiCSSOptions>;
1830
}
1931

2032
class BroccoliCSSBlocks extends BroccoliPlugin {
2133

2234
private analyzer: Analyzer<keyof TemplateTypes>;
23-
private entry: string[];
35+
private entries: string[];
2436
private output: string;
25-
private transport: { [key: string]: object };
37+
private root: string;
38+
private transport: Transport;
2639
private optimizationOptions: Partial<OptiCSSOptions>;
27-
2840
// tslint:disable-next-line:prefer-whatever-to-any
2941
constructor(inputNode: any, options: BroccoliOptions) {
3042
super([inputNode], { name: "broccoli-css-blocks" });
3143

32-
this.entry = options.entry;
33-
this.output = options.output;
34-
this.analyzer = options.analyzer;
44+
this.entries = options.entry.slice(0);
45+
this.output = options.output || "css-blocks.css";
3546
this.transport = options.transport;
3647
this.optimizationOptions = options.optimization || {};
37-
38-
if (!this.output) {
39-
throw new Error("CSS Blocks Broccoli Plugin requires an output file name.");
40-
}
48+
this.analyzer = options.analyzer;
49+
this.root = options.root || process.cwd();
50+
this.transport.css = this.transport.css ? this.transport.css : "";
4151
}
4252

4353
async build() {
4454
let options = this.analyzer.cssBlocksOptions;
4555
let blockCompiler = new BlockCompiler(postcss, options);
4656
let optimizer = new Optimizer(this.optimizationOptions, this.analyzer.optimizationOptions);
4757

58+
// When no entry points are passed, we treat *every* template as an entry point.
59+
let discover = !this.entries.length;
60+
4861
// This build step is *mostly* just a pass-through of all files!
4962
// QUESTION: Tom, is there a better way to do this in Broccoli?
5063
let files = await readdir(this.inputPaths[0]);
5164
for (let file of files) {
5265
file = path.relative(this.inputPaths[0], file);
53-
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)));
5471
try {
55-
await fs.symlink(
72+
fs.symlinkSync(
5673
path.join(this.inputPaths[0], file),
5774
path.join(this.outputPath, file),
5875
);
5976
} catch (e) {
6077
// tslint:disable-next-line:no-console
6178
console.log("Error linking", path.join(this.inputPaths[0], file), "to output directory.");
6279
}
80+
6381
}
6482

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+
6594
// Oh hey look, we're analyzing.
66-
await this.analyzer.analyze(...this.entry);
95+
this.analyzer.reset();
96+
await this.analyzer.analyze(this.outputPath, this.entries);
6797

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

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

84114
// Add the compiled Block file to the optimizer.
@@ -101,13 +131,9 @@ class BroccoliCSSBlocks extends BroccoliPlugin {
101131
this.transport.mapping = styleMapping;
102132
this.transport.blocks = blocks;
103133
this.transport.analyzer = this.analyzer;
104-
this.transport.css = optimized.output;
134+
this.transport.css += optimized.output.content.toString();
105135

106-
// Write our compiled CSS to the output tree.
107-
await fs.writeFile(
108-
path.join(this.outputPath, this.output),
109-
optimized.output.content.toString(),
110-
);
136+
debug(`Compilation Finished: ${this.transport.id}`);
111137

112138
}
113139

packages/@css-blocks/broccoli/test/plugin-test.ts

+24-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { GlimmerAnalyzer } from "@css-blocks/glimmer";
21
import * as assert from "assert";
2+
import * as path from "path";
3+
4+
import { GlimmerAnalyzer } from "@css-blocks/glimmer";
35
import { TempDir, buildOutput, createTempDir } from "broccoli-test-helper";
46

57
import { BroccoliCSSBlocks } from "../src/index";
@@ -32,38 +34,47 @@ describe("Broccoli Plugin Test", function () {
3234
components: {
3335
[entryComponentName]: {
3436
"template.hbs": `<div><h1 class="foo">Welcome to Glimmer!</h1></div>`,
35-
"stylesheet.block.css": `:scope {
36-
color: red;
37-
}
37+
"stylesheet.css": `
38+
:scope {
39+
color: red;
40+
}
3841
39-
.foo {
40-
color: green;
41-
}`,
42+
.foo {
43+
color: green;
44+
}
45+
`,
4246
},
4347
},
4448
},
4549
},
4650
});
4751

48-
let analyzer = new GlimmerAnalyzer(input.path());
49-
let transport = {};
52+
let transport = { id: "test-transport" };
53+
let analyzer = new GlimmerAnalyzer({}, {}, {
54+
app: { name: "test" },
55+
types: {
56+
stylesheet: { definitiveCollection: "components" },
57+
template: { definitiveCollection: "components" },
58+
},
59+
collections: {
60+
components: { group: "ui", types: [ "template", "stylesheet" ] },
61+
},
62+
});
5063

5164
let compiler = new BroccoliCSSBlocks(input.path(), {
5265
entry: [entryComponentName],
66+
root: path.join(__dirname, "../.."),
5367
output: "css-blocks.css",
5468
transport,
5569
analyzer,
5670
});
57-
58-
let output = await buildOutput(compiler);
59-
let files = output.read();
71+
await buildOutput(compiler);
6072

6173
assert.ok(Object.keys(transport).length, "Transport Object populated");
6274
assert.ok(transport["mapping"], "Mapping property is populated in Transport Object");
6375
assert.ok(transport["blocks"], "Blocks property is populated in Transport Object");
6476
assert.ok(transport["analyzer"], "Analyzer property is populated in Transport Object");
6577
assert.ok(transport["css"], "CSS property is populated in Transport Object");
66-
assert.ok(files["css-blocks.css"], "CSS File generated");
6778
});
6879
});
6980
});

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.

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);

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
}

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
}

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

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
}

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)