Skip to content

Commit 92dd4c8

Browse files
committed
fix: Ember fixture app and addons for testing @css-blocks/ember.
1 parent bb55671 commit 92dd4c8

File tree

206 files changed

+4565
-69
lines changed

Some content is hidden

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

206 files changed

+4565
-69
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"private": true,
44
"workspaces": [
55
"packages/@css-blocks/*",
6-
"private-packages/fixtures/*"
6+
"private-packages/fixtures/*",
7+
"private-packages/fixtures-ember-v2/*"
78
],
89
"scripts": {
910
"ci": "lerna run test",

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@
3939
"broccoli-node-api": "^1.7.0",
4040
"broccoli-test-helper": "^2.0.0",
4141
"ember-cli-htmlbars": "^4.3.1",
42-
"fs-merger": "^3.0.2",
43-
"broccoli-output-wrapper": "^3.2.1",
4442
"typescript": "~3.8.3",
4543
"watch": "^1.0.2"
4644
},
@@ -54,12 +52,15 @@
5452
"@glimmer/syntax": "^0.43.0",
5553
"@opticss/template-api": "^0.6.3",
5654
"@opticss/util": "^0.7.0",
57-
"broccoli-funnel": "^3.0.0",
55+
"broccoli-debug": "^0.6.5",
56+
"broccoli-funnel": "^3.0.2",
5857
"broccoli-merge-trees": "^4.0.0",
58+
"broccoli-output-wrapper": "^3.2.1",
5959
"broccoli-plugin": "^4.0.0",
6060
"colors": "^1.2.1",
6161
"debug": "^4.1.1",
6262
"fs-extra": "^8.0.0",
63+
"fs-merger": "^3.0.2",
6364
"fs-tree-diff": "^2.0.0",
6465
"glob": "^7.1.2",
6566
"opticss": "^0.7.0",

packages/@css-blocks/ember/src/AnalyzingRewriteManager.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export class AnalyzingRewriteManager {
8484
let block = this.templateBlocks[templatePath];
8585
let template = new HandlebarsTemplate(templatePath, templatePath);
8686
this.templates.set(templatePath, template);
87-
let analysis = new EmberAnalysis(template, this.validationOptions);
87+
let analysis = new EmberAnalysis(template, block, this.validationOptions);
8888
this.analyses.set(templatePath, analysis);
8989
return new TemplateAnalyzingRewriter(template, block, analysis, this.cssBlocksOpts, syntax);
9090
}
@@ -108,12 +108,9 @@ export class AnalyzingRewriteManager {
108108
for (let templatePath of templatePaths) {
109109
let block = this.templateBlocks[templatePath]!;
110110
let template = this.templates.get(templatePath);
111-
if (!template) {
112-
throw new CssBlockError(`Internal Error: Template at ${templatePath} was not yet analyzed.`);
113-
}
114111
let analysis = this.analyses.get(templatePath);
115-
if (!analysis) {
116-
throw new CssBlockError(`Internal Error: Template at ${templatePath} was not yet analyzed.`);
112+
if (!template || !analysis) {
113+
continue; // this template didn't change so it didn't get compiled.
117114
}
118115
yield {
119116
template,

packages/@css-blocks/ember/src/BroccoliFileLocator.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class BroccoliFileLocator implements FileLocator {
1717
return `broccoli-tree:${relativePathToStylesheet}`;
1818
}
1919
possibleTemplatePaths(): Array<string> {
20-
return this.fs.entries(".", ["**/*.hbs"]).map(e => e.basePath);
20+
return this.fs.entries(".", ["**/*.hbs"]).map(e => e.relativePath);
2121
}
2222
possibleStylesheetPathsForTemplate(templatePath: string, extensions: Array<string>): Array<string> {
2323
let path = templatePath.replace("/templates/", "/styles/");

packages/@css-blocks/ember/src/BroccoliTreeImporter.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@ export class BroccoliTreeImporter implements Importer {
3333
identifier(fromIdentifier: string | null, importPath: string, config: Readonly<Configuration>): string {
3434
if (isBroccoliTreeIdentifier(fromIdentifier)) {
3535
if (importPath.startsWith("./") || importPath.startsWith("../")) {
36-
return pathToIdent(path.resolve(path.dirname(identToPath(fromIdentifier)), importPath));
36+
let parsedPath = path.parse(identToPath(fromIdentifier));
37+
// We have to make resolve think the path is absolute or else it will
38+
// prepend the current working directory.
39+
let dir = "/" + parsedPath.dir;
40+
// then we take the `/` off again to make it relative to the broccoli tree.
41+
let relativePath = path.resolve(dir, importPath).substring(1);
42+
return pathToIdent(relativePath);
3743
} else {
3844
return this.fallbackImporter.identifier(null, importPath, config);
3945
}
@@ -47,7 +53,7 @@ export class BroccoliTreeImporter implements Importer {
4753
let relativePath = identToPath(identifier);
4854
let contents = this.input.readFileSync(relativePath, "utf8");
4955
let syntax = syntaxFromExtension(path.extname(relativePath));
50-
let defaultName = path.basename(relativePath);
56+
let defaultName = path.parse(relativePath).name;
5157
defaultName = defaultName.replace(/.block$/, "");
5258
return {
5359
identifier,
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import { AnalysisImpl, TemplateValidatorOptions } from "@css-blocks/core";
1+
import { AnalysisImpl, Block, DEFAULT_EXPORT, TemplateValidatorOptions } from "@css-blocks/core";
22

33
import { HandlebarsTemplate, TEMPLATE_TYPE as HANDLEBARS_TEMPLATE } from "./HandlebarsTemplate";
44

55
export class EmberAnalysis extends AnalysisImpl<HANDLEBARS_TEMPLATE> {
6-
constructor(template: HandlebarsTemplate, options: TemplateValidatorOptions) {
6+
constructor(template: HandlebarsTemplate, block: Block | undefined, options: TemplateValidatorOptions) {
77
super(template, options);
8+
if (block) {
9+
this.addBlock(DEFAULT_EXPORT, block);
10+
}
811
}
912
}

packages/@css-blocks/ember/src/TemplateAnalyzingRewriter.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { HandlebarsTemplate, TEMPLATE_TYPE } from "./HandlebarsTemplate";
2222
const NOOP_VISITOR = {};
2323
const DEBUG = debugGenerator("css-blocks:glimmer:analyzing-rewriter");
2424

25-
interface ASTPluginWithDeps extends ASTPlugin {
25+
export interface ASTPluginWithDeps extends ASTPlugin {
2626
/**
2727
* If this method exists, it is called with the relative path to the current
2828
* file just before processing starts. Use this method to reset the
@@ -37,7 +37,7 @@ interface ASTPluginWithDeps extends ASTPlugin {
3737
* file. Any relative paths returned by this method are taken to be relative
3838
* to the file that was processed.
3939
*/
40-
dependencies(relativePath: string): string[];
40+
dependencies?(relativePath: string): string[];
4141
}
4242

4343
export class TemplateAnalyzingRewriter implements ASTPluginWithDeps {

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

+84-42
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
import config from "@css-blocks/config";
22
import { AnalysisOptions, Block, BlockCompiler, BlockFactory, Configuration, NodeJsImporter, Options as ParserOptions, OutputMode, resolveConfiguration } from "@css-blocks/core";
3-
import type { AST, ASTPlugin, ASTPluginEnvironment, NodeVisitor, Syntax } from "@glimmer/syntax";
3+
import type { ASTPlugin, ASTPluginEnvironment } from "@glimmer/syntax";
44
import { ObjectDictionary } from "@opticss/util";
5+
import BroccoliDebug = require("broccoli-debug");
6+
import funnel = require("broccoli-funnel");
57
import type { InputNode } from "broccoli-node-api";
6-
import TemplateCompilerPlugin, { HtmlBarsOptions } from "ember-cli-htmlbars/lib/template-compiler-plugin";
8+
import outputWrapper = require("broccoli-output-wrapper");
9+
import TemplateCompilerPlugin = require("ember-cli-htmlbars/lib/template-compiler-plugin");
710
import type EmberApp from "ember-cli/lib/broccoli/ember-app";
811
import type EmberAddon from "ember-cli/lib/models/addon";
912
import type { AddonImplementation, ThisAddon, Tree } from "ember-cli/lib/models/addon";
13+
import type Project from "ember-cli/lib/models/project";
14+
import FSMerger = require("fs-merger");
1015
import * as FSTree from "fs-tree-diff";
1116
import { OptiCSSOptions, postcss } from "opticss";
1217
import * as path from "path";
1318

1419
import { AnalyzingRewriteManager } from "./AnalyzingRewriteManager";
20+
import { BroccoliFileLocator } from "./BroccoliFileLocator";
1521
import { BroccoliTreeImporter, identToPath, isBroccoliTreeIdentifier } from "./BroccoliTreeImporter";
1622
import { EmberAnalysis } from "./EmberAnalysis";
17-
import { BroccoliFileLocator } from "./BroccoliFileLocator";
23+
import { ASTPluginWithDeps } from "./TemplateAnalyzingRewriter";
1824

1925
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
2026

@@ -25,37 +31,33 @@ interface EmberASTPluginEnvironment extends ASTPluginEnvironment {
2531
};
2632
}
2733

28-
class Visitor implements NodeVisitor {
29-
moduleName: string;
30-
syntax: Syntax;
31-
constructor(moduleName: string, syntax: Syntax) {
32-
this.moduleName = moduleName;
33-
this.syntax = syntax;
34-
}
35-
ElementNode(node: AST.ElementNode) {
36-
console.log(`visited ${this.syntax.print(node)}`);
37-
}
34+
function withoutCssBlockFiles(tree: InputNode | undefined) {
35+
if (!tree) return tree;
36+
return funnel(tree, {
37+
exclude: ["**/*.block.{css,scss,sass,less,styl}"],
38+
});
3839
}
39-
const NOOP_VISITOR = {};
4040

4141
class CSSBlocksTemplateCompilerPlugin extends TemplateCompilerPlugin {
4242
previousSourceTree: FSTree;
4343
cssBlocksOptions: CSSBlocksEmberOptions;
4444
parserOpts: Readonly<Configuration>;
4545
analyzingRewriter: AnalyzingRewriteManager | undefined;
46-
constructor(inputTree: InputNode, htmlbarsOptions: HtmlBarsOptions, cssBlocksOptions: CSSBlocksEmberOptions) {
47-
htmlbarsOptions.plugins = htmlbarsOptions.plugins || {};
48-
htmlbarsOptions.plugins.ast = htmlbarsOptions.plugins.ast || [];
49-
htmlbarsOptions.plugins.ast.unshift((env) => this.astPluginBuilder(env));
46+
input!: FSMerger.FS;
47+
output!: outputWrapper.FSOutput;
48+
constructor(inputTree: InputNode, htmlbarsOptions: TemplateCompilerPlugin.HtmlBarsOptions, cssBlocksOptions: CSSBlocksEmberOptions) {
5049
super(inputTree, htmlbarsOptions);
5150
this.cssBlocksOptions = cssBlocksOptions;
5251
this.parserOpts = resolveConfiguration(cssBlocksOptions.parserOpts);
5352
this.previousSourceTree = new FSTree();
5453
}
55-
astPluginBuilder(env: EmberASTPluginEnvironment) {
54+
astPluginBuilder(env: EmberASTPluginEnvironment): ASTPluginWithDeps {
5655
let moduleName = env.meta?.["moduleName"];
5756
if (!moduleName) {
58-
throw new Error("[internal error] moduleName expected.");
57+
return {
58+
name: "css-blocks-noop",
59+
visitor: {},
60+
};
5961
}
6062
if (!this.analyzingRewriter) {
6163
throw new Error("[internal error] analyzing rewriter expected.");
@@ -67,7 +69,14 @@ class CSSBlocksTemplateCompilerPlugin extends TemplateCompilerPlugin {
6769
// write additional output files to the output tree.
6870
return this.analyzingRewriter.templateAnalyzerAndRewriter(moduleName, env.syntax);
6971
}
72+
7073
async build() {
74+
if (!this.input) {
75+
this.input = new FSMerger(this.inputPaths).fs;
76+
}
77+
if (!this.output) {
78+
this.output = outputWrapper(this);
79+
}
7180
let cssBlockEntries = this.input.entries(".", [BLOCK_GLOB]);
7281
let currentFSTree = FSTree.fromEntries(cssBlockEntries);
7382
let patch = this.previousSourceTree.calculatePatch(currentFSTree);
@@ -85,12 +94,22 @@ class CSSBlocksTemplateCompilerPlugin extends TemplateCompilerPlugin {
8594
// the blocks and associate them to their corresponding templates.
8695
await this.analyzingRewriter.discoverTemplatesWithBlocks();
8796
// Compiles the handlebars files, runs our plugin for each file
88-
await super.build();
97+
// we have to wrap this RSVP Promise that's returned in a native promise or
98+
// else await won't work.
99+
let builder = new Promise((resolve, reject) => {
100+
try {
101+
let buildResult = super.build() || Promise.resolve();
102+
buildResult.then(resolve, reject);
103+
} catch (e) {
104+
reject(e);
105+
}
106+
});
107+
await builder;
89108
// output compiled block files and template analyses
90109
let blocks = new Set<Block>();
91110
let blockOutputPaths = new Map<Block, string>();
92111
let analyses = new Array<EmberAnalysis>();
93-
for (let analyzedTemplate of this.analyzingRewriter.analyzedTemplates()) {
112+
for (let analyzedTemplate of this.analyzingRewriter!.analyzedTemplates()) {
94113
let { block, analysis } = analyzedTemplate;
95114
analyses.push(analysis);
96115
blocks.add(block);
@@ -105,15 +124,15 @@ class CSSBlocksTemplateCompilerPlugin extends TemplateCompilerPlugin {
105124
if (!block.stylesheet) {
106125
throw new Error("[internal error] block stylesheet expected.");
107126
}
108-
// TODO generate definition file too
109-
let compiledAST = compiler.compile(block, block.stylesheet, this.analyzingRewriter.reservedClassNames());
127+
// TODO generate definition file too1
128+
let compiledAST = compiler.compile(block, block.stylesheet, this.analyzingRewriter!.reservedClassNames());
110129
// TODO disable source maps in production?
111130
let result = compiledAST.toResult({ to: outputPath, map: { inline: true } });
112-
this.output.writeFileSync(outputPath, result.css, "utf8");
131+
this.output.writeFileSync(outputPath, wrapCSSWithDelimiterComments(block.guid, result.css), "utf8");
113132
}
114133
for (let analysis of analyses) {
115134
let analysisOutputPath = analysisPath(analysis.template.relativePath);
116-
this.output.mkdirSync(analysisOutputPath, { recursive: true });
135+
this.output.mkdirSync(path.dirname(analysisOutputPath), { recursive: true });
117136
this.output.writeFileSync(
118137
analysisOutputPath,
119138
JSON.stringify(analysis.serialize(blockOutputPaths)),
@@ -123,6 +142,12 @@ class CSSBlocksTemplateCompilerPlugin extends TemplateCompilerPlugin {
123142
}
124143
}
125144

145+
// This is a placeholder, eventually the block compiler should add this.
146+
function wrapCSSWithDelimiterComments(guid: string, css: string) {
147+
return `/*#css-blocks ${guid}*/\n${css}\n/*#css-blocks end*/\n`;
148+
}
149+
150+
126151
function analysisPath(templatePath: string): string {
127152
let analysisPath = path.parse(templatePath);
128153
delete analysisPath.base;
@@ -132,7 +157,7 @@ function analysisPath(templatePath: string): string {
132157

133158
function getOutputPath(block: Block): string {
134159
if (isBroccoliTreeIdentifier(block.identifier)) {
135-
return identToPath(block.identifier);
160+
return identToPath(block.identifier).replace(".block", "");
136161
} else {
137162
throw new Error("Implement me!");
138163
}
@@ -154,17 +179,18 @@ export interface CSSBlocksEmberOptions {
154179
}
155180

156181
interface CSSBlocksAddon {
182+
templateCompiler?: CSSBlocksTemplateCompilerPlugin;
157183
findSiblingAddon<AddonType>(this: ThisAddon<CSSBlocksAddon>, name: string): ThisAddon<AddonType> | undefined;
158184
getOptions(this: ThisAddon<CSSBlocksAddon>): CSSBlocksEmberOptions;
159185
optionsForCacheInvalidation(this: ThisAddon<CSSBlocksAddon>): ObjectDictionary<unknown>;
160186
astPluginBuilder(env: EmberASTPluginEnvironment): ASTPlugin;
161187
_options?: CSSBlocksEmberOptions;
162188
}
163189
interface HTMLBarsAddon {
164-
getTemplateCompiler(inputTree: Tree, htmlbarsOptions: HtmlBarsOptions): TemplateCompilerPlugin;
190+
getTemplateCompiler(inputTree: Tree, htmlbarsOptions: TemplateCompilerPlugin.HtmlBarsOptions): TemplateCompilerPlugin;
165191
}
166192

167-
function isAddon(parent: EmberAddon | EmberApp): parent is EmberAddon {
193+
function isAddon(parent: EmberAddon | EmberApp | Project): parent is EmberAddon {
168194
return !!parent["findOwnAddonByName"];
169195
}
170196

@@ -173,36 +199,52 @@ const EMBER_ADDON: AddonImplementation<CSSBlocksAddon> = {
173199

174200
init(parent, project) {
175201
this._super.init.call(this, parent, project);
176-
this.app = this._findHost();
177202
},
178203

179204
findSiblingAddon(name) {
180205
if (isAddon(this.parent)) {
181206
return this.parent.findOwnAddonByName(name);
182207
} else {
183-
this.project.findAddonByName(name);
208+
return this.project.findAddonByName(name);
184209
}
185210
},
186211

187212
included(parent) {
188213
this._super.included.apply(this, [parent]);
214+
this.app = this._findHost();
215+
let parentName = typeof parent.name === "string" ? parent.name : parent.name();
189216
this._options = this.getOptions();
190217
let htmlBarsAddon = this.findSiblingAddon<HTMLBarsAddon>("ember-cli-htmlbars");
191218
if (!htmlBarsAddon) {
192-
throw new Error(`Using @css-blocks/ember on ${this.parent.name} also requires ember-cli-htmlbars to be an addon for ${this.parent.name}`);
219+
throw new Error(`Using @css-blocks/ember on ${parentName} also requires ember-cli-htmlbars to be an addon for ${parentName} (ember-cli-htmlbars should be a dependency in package.json, not a devDependency)`);
193220
}
194-
htmlBarsAddon.getTemplateCompiler = (inputTree: Tree, htmlbarsOptions: HtmlBarsOptions) => {
195-
return new CSSBlocksTemplateCompilerPlugin(inputTree, htmlbarsOptions, this._options!);
221+
if (!htmlBarsAddon.getTemplateCompiler) {
222+
throw new Error("This version of ember-cli-htmlbars is not compatible with @css-blocks/ember. Please upgrade.");
223+
}
224+
htmlBarsAddon.getTemplateCompiler = (inputTree: Tree, htmlbarsOptions: TemplateCompilerPlugin.HtmlBarsOptions) => {
225+
this.templateCompiler = new CSSBlocksTemplateCompilerPlugin(inputTree, htmlbarsOptions, this._options!);
226+
return this.templateCompiler;
196227
};
197228
},
198229

199-
astPluginBuilder(env: EmberASTPluginEnvironment): ASTPlugin {
200-
let {meta, syntax } = env;
201-
let moduleName = meta?.moduleName;
202-
return {
203-
name: `CSS Blocks AST Plugin for ${moduleName}`,
204-
visitor: moduleName ? new Visitor(moduleName, syntax) : NOOP_VISITOR,
205-
};
230+
astPluginBuilder(env) {
231+
return this.templateCompiler!.astPluginBuilder(env);
232+
},
233+
234+
preprocessTree(type, tree) {
235+
if (type !== "css") return tree;
236+
// We compile CSS Block files in the template tree, so in the CSS Tree all
237+
// we need to do is prune them out of the build before the tree gets
238+
// built.
239+
return withoutCssBlockFiles(tree);
240+
},
241+
242+
postprocessTree(type, tree) {
243+
if (type !== "template") return tree;
244+
tree = withoutCssBlockFiles(tree);
245+
let parentName = typeof this.parent.name === "string" ? this.parent.name : this.parent.name();
246+
let isAddon = typeof this.parent.name === "string";
247+
return new BroccoliDebug(tree, `css-blocks:template-output:${parentName}:${isAddon ? "addon" : "app"}`);
206248
},
207249

208250
setupPreprocessorRegistry(type, registry) {
@@ -250,7 +292,7 @@ const EMBER_ADDON: AddonImplementation<CSSBlocksAddon> = {
250292
}
251293
options.parserOpts.outputMode = OutputMode.BEM_UNIQUE;
252294

253-
if (typeof options.output !== "string") {
295+
if (options.output !== undefined && typeof options.output !== "string") {
254296
throw new Error(`Invalid css-blocks options in 'ember-cli-build.js': Output must be a string. Instead received ${options.output}.`);
255297
}
256298
return options;

0 commit comments

Comments
 (0)