Skip to content

Commit da47ce6

Browse files
Tim Lindvalltimlindvall
Tim Lindvall
authored andcommittedSep 3, 2020
feat: Update concatenation. WIP sourcemaps fix.
- Use broccoli-concat to merge files together, which can better handle sourcemaps that are present. - Update BaseImporter to ensure that sourcemaps are included in parsed CSS content from compiled css files.
1 parent 98d0e76 commit da47ce6

File tree

7 files changed

+142
-343
lines changed

7 files changed

+142
-343
lines changed
 

‎packages/@css-blocks/core/src/BlockParser/BlockFactory.ts

-2
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,6 @@ export class BlockFactory {
346346
const cssContentsAst = this.postcssImpl.parse(file.cssContents);
347347
const cssDebugIdentifier = this.importer.debugIdentifier(file.identifier, this.configuration);
348348

349-
// TODO: Sourcemaps?
350-
351349
// Sanity check! Did we actually get contents for both ASTs?
352350
if (!definitionAst || !definitionAst.nodes) {
353351
throw new CssBlockError(`Unable to parse definition file into AST!`, {

‎packages/@css-blocks/core/src/PrecompiledDefinitions/compiled-comments.ts

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ export const REGEXP_COMMENT_DEFINITION_REF = /^\/\*#blockDefinitionURL=([\S]+?)\
1616
*/
1717
export const REGEXP_COMMENT_FOOTER = /^\/\*#css-blocks end\*\/\r?\n?/m;
1818

19+
/**
20+
* A regular expression that can be used to find the included sourcemap in the file.
21+
*/
22+
export const REGEXP_SOURCEMAP_COMMENT = /(^\/\*#\s*sourceMappingURL=\S+\s*\*\/$|^\/\/#\s*sourceMappingURL=\S+\s*$)/m;
23+
1924
/**
2025
* Determines if the given URL for a definition is valid.
2126
*

‎packages/@css-blocks/core/src/importing/BaseImporter.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Syntax } from "../BlockParser";
22
import { ResolvedConfiguration } from "../configuration";
3-
import { REGEXP_COMMENT_DEFINITION_REF, REGEXP_COMMENT_FOOTER, REGEXP_COMMENT_HEADER } from "../PrecompiledDefinitions/compiled-comments";
3+
import { REGEXP_COMMENT_DEFINITION_REF, REGEXP_COMMENT_FOOTER, REGEXP_COMMENT_HEADER, REGEXP_SOURCEMAP_COMMENT } from "../PrecompiledDefinitions/compiled-comments";
44

55
import { FileIdentifier, ImportedCompiledCssFile, ImportedCompiledCssFileContents, ImportedFile, Importer } from "./Importer";
66

@@ -103,13 +103,32 @@ export abstract class BaseImporter implements Importer {
103103
}
104104
const [definitionFullMatch, definitionUrl] = definitionRegexpResult;
105105
const blockCssContents = fullBlockContents.replace(definitionFullMatch, "");
106+
const blockContentsWithSourcemap = this.mergeSourcemapIntoCompiledCssContents(blockCssContents, post);
106107

107108
return {
108109
pre,
109110
blockId,
110-
blockCssContents,
111+
blockCssContents: blockContentsWithSourcemap,
111112
definitionUrl,
112113
post,
113114
};
114115
}
116+
117+
/**
118+
* Look for sourcemap data in given post-blocks content. If found, append it to the given CSS contents.
119+
* @param cssContents - The CSS contents of the Compiled CSS file (what appears between the CSS Blocks comments).
120+
* @param postContent - Content that appears in the content after the closing CSS Blocks comment.
121+
* @returns The CSS Contents with sourcemap appended, if found. Returns CSS contents alone if not found.
122+
*/
123+
private mergeSourcemapIntoCompiledCssContents(cssContents: string, postContent: string) {
124+
const sourcemapRegexpResult = postContent.match(REGEXP_SOURCEMAP_COMMENT);
125+
126+
// No sourcemap? Just return the CSS contents.
127+
if (sourcemapRegexpResult === null) {
128+
return cssContents;
129+
}
130+
131+
// Otherwise, append the sourcemap to the end of the CSS contents.
132+
return `${cssContents}\n${sourcemapRegexpResult[0]}`;
133+
}
115134
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"@glimmer/syntax": "^0.43.0",
5757
"@opticss/template-api": "^0.7.0",
5858
"@opticss/util": "^0.7.0",
59+
"broccoli-concat": "^4.2.4",
5960
"broccoli-debug": "^0.6.5",
6061
"broccoli-funnel": "^3.0.3",
6162
"broccoli-merge-trees": "^4.2.0",

‎packages/@css-blocks/ember-app/src/broccoli-plugin.ts

+38-44
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,15 @@ export class CSSBlocksApplicationPlugin extends Filter {
8282
debug(`Loaded ${blocksUsed.size} blocks.`);
8383
debug(`Loaded ${optimizer.analyses.length} analyses.`);
8484
let cssFileName = cssBlocksOutputFilename(this.cssBlocksOptions);
85-
let sourceMapFileName = `${cssFileName}.map`;
8685
let optLogFileName = `${cssFileName}.optimization.log`;
8786
let optimizationResult = await optimizer.optimize(cssFileName);
8887
debug(`Optimized CSS. There were ${optimizationResult.actions.performed.length} optimizations performed.`);
88+
89+
// Embed the sourcemap into the final css output
90+
const finalizedCss = addSourcemapInfoToOptimizedCss(optimizationResult.output.content.toString(), optimizationResult.output.sourceMap?.toString());
91+
8992
this.output.mkdirSync(path.dirname(cssFileName), {recursive: true});
90-
this.output.writeFileSync(cssFileName, optimizationResult.output.content.toString(), "utf8");
91-
this.output.writeFileSync(sourceMapFileName, optimizationResult.output.sourceMap?.toString(), "utf8");
93+
this.output.writeFileSync(cssFileName, finalizedCss, "utf8");
9294
this.output.writeFileSync(optLogFileName, optimizationResult.actions.logStrings().join("\n"), "utf8");
9395
debug("Wrote css, sourcemap, and optimization log.");
9496

@@ -145,11 +147,10 @@ export class CSSBlocksApplicationPlugin extends Filter {
145147
* 1) The result of the CSSBlocksApplicationPlugin.
146148
* 2) The css tree, passed in to `preprocessTree()`.
147149
*
148-
* The result of this plugin will be a file in app/styles/app.css that includes existing content appended
149-
* with the contents of css-blocks.css. This should be merged with the existing css tree to overwrite
150-
* the app.css file with this one.
150+
* This plugin will add the compiled CSS content created by the CSSBlocksApplicationPlugin and place
151+
* it into the CSS tree at the preferred location.
151152
*/
152-
export class CSSBlocksStylesProcessorPlugin extends Plugin {
153+
export class CSSBlocksStylesPreprocessorPlugin extends Plugin {
153154
appName: string;
154155
previousSourceTree: FSTree;
155156
cssBlocksOptions: ResolvedCSSBlocksEmberOptions;
@@ -160,63 +161,56 @@ export class CSSBlocksStylesProcessorPlugin extends Plugin {
160161
this.cssBlocksOptions = cssBlocksOptions;
161162
}
162163
async build() {
163-
let mergeIntoAppStyles = true;
164-
if (this.cssBlocksOptions.output) {
165-
// if the output filename is explicitly declared, we don't merge it with
166-
// the application styles.
167-
mergeIntoAppStyles = false;
168-
}
169-
// Read the optimized CSS Blocks styles file, generated previously by the CSSBlocksApplicationPlugin.
164+
// Are there any changes to make? If not, bail out early.
170165
let stylesheetPath = cssBlocksOutputFilename(this.cssBlocksOptions);
171-
let applicationStylesheetPath = `app/styles/app.css`;
172-
if (!this.input.existsSync(applicationStylesheetPath)) {
173-
mergeIntoAppStyles = false;
174-
debug(`No app/styles/app.css file found. Blocks content is in ${stylesheetPath}. The application should handle it.`);
175-
}
176-
let globs: Array<string>;
177-
if (mergeIntoAppStyles) {
178-
globs = [stylesheetPath, applicationStylesheetPath];
179-
} else {
180-
globs = [stylesheetPath];
181-
}
182-
let entries = this.input.entries(".", {globs});
166+
let entries = this.input.entries(".", {globs: [stylesheetPath]});
183167
let currentFSTree = FSTree.fromEntries(entries);
184168
let patch = this.previousSourceTree.calculatePatch(currentFSTree);
185169
if (patch.length === 0) {
186170
return;
187171
} else {
188172
this.previousSourceTree = currentFSTree;
189173
}
174+
// Read in the CSS Blocks compiled content that was created previously.
190175
let blocksFileContents: string;
191-
// And read the application CSS that was previously built by Ember and ignored by CSS Blocks.
192176
if (this.input.existsSync(stylesheetPath)) {
193177
blocksFileContents = this.input.readFileSync(stylesheetPath, { encoding: "utf8" });
194178
} else {
195179
// We always write the output file if this addon is installed, even if
196180
// there's no css-blocks files.
197181
blocksFileContents = "";
198182
}
199-
let outputContents: string;
200-
let outputPath: string;
201-
if (mergeIntoAppStyles) {
202-
const appCssFileContents = this.input.readFileSync(applicationStylesheetPath, { encoding: "utf8" });
203-
if (blocksFileContents) {
204-
outputContents = `${appCssFileContents}\n${blocksFileContents}`;
205-
} else {
206-
outputContents = appCssFileContents;
207-
}
208-
outputPath = applicationStylesheetPath;
209-
} else {
210-
outputContents = blocksFileContents;
211-
outputPath = stylesheetPath;
212-
}
213183

214-
// Now, write out the combined result of the application CSS and CSS Blocks contents.
215-
this.output.mkdirSync(path.dirname(outputPath), { recursive: true });
216-
this.output.writeFileSync(outputPath, outputContents);
184+
// Now, write out compiled content to its expected location.
185+
// By default, this is app/styles/css-blocks.css.
186+
this.output.mkdirSync(path.dirname(stylesheetPath), { recursive: true });
187+
this.output.writeFileSync(stylesheetPath, blocksFileContents);
217188
}
218189
}
219190

191+
/**
192+
* Given CSS and a sourcemap, append an embedded sourcemap (base64 encoded)
193+
* to the end of the css.
194+
* @param css - The CSS content to be added to.
195+
* @param sourcemap - The sourcemap data to add.
196+
* @returns - The CSS with embedded sourcemap, or just the CSS if no sourcemap was given.
197+
*/
198+
function addSourcemapInfoToOptimizedCss(css: string, sourcemap?: string) {
199+
if (!sourcemap) {
200+
return css;
201+
}
202+
203+
const encodedSourcemap = Buffer.from(sourcemap).toString("base64");
204+
return `${css}\n/*# sourceMappingURL=data:application/json;base64,${encodedSourcemap} */`;
205+
}
206+
207+
/**
208+
* Generate the output path for the compiled CSS Blocks content, using the
209+
* preferred filename given by the user. If none is given, the default
210+
* path is "app/styles/css-blocks.css".
211+
* @param options - The options passed to the Ember plugin.
212+
* @returns - The path for the CSS Blocks compiled content.
213+
*/
220214
function cssBlocksOutputFilename(options: ResolvedCSSBlocksEmberOptions) {
221215
let outputName = options.output || "css-blocks.css";
222216
return `app/styles/${outputName}`;

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

+35-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CSSBlocksEmberOptions, ResolvedCSSBlocksEmberOptions, getConfig } from "@css-blocks/ember-utils";
2+
import broccoliConcat = require("broccoli-concat");
23
import BroccoliDebug = require("broccoli-debug");
34
import funnel = require("broccoli-funnel");
45
import mergeTrees = require("broccoli-merge-trees");
@@ -7,7 +8,7 @@ import type Addon from "ember-cli/lib/models/addon";
78
import type { AddonImplementation, ThisAddon } from "ember-cli/lib/models/addon";
89
import Project from "ember-cli/lib/models/project";
910

10-
import { CSSBlocksApplicationPlugin, CSSBlocksStylesProcessorPlugin } from "./broccoli-plugin";
11+
import { CSSBlocksApplicationPlugin, CSSBlocksStylesPreprocessorPlugin } from "./broccoli-plugin";
1112

1213
interface AddonEnvironment {
1314
parent: Addon | EmberApp;
@@ -172,6 +173,10 @@ const EMBER_ADDON: AddonImplementation<CSSBlocksApplicationAddon> = {
172173
return tree;
173174
}
174175
} else if (type === "css") {
176+
// TODO: We shouldn't need to use a custom plugin here anymore.
177+
// Refactor this to use simple broccoli filters and merges.
178+
// (This means the prev. plugin becomes reponsible for putting
179+
// the css file in the right directory.)
175180
if (!env.isApp) {
176181
return tree;
177182
}
@@ -182,12 +187,40 @@ const EMBER_ADDON: AddonImplementation<CSSBlocksApplicationAddon> = {
182187
throw new Error("[css-blocks/ember-app] The CSS tree ran before the JS tree, so the CSS tree doesn't have the contents for CSS Blocks files. This shouldn't ever happen, but if it does, please file an issue with us!");
183188
}
184189
// Get the combined CSS file
185-
const cssBlocksContentsTree = new CSSBlocksStylesProcessorPlugin(env.modulePrefix, env.config, [this.broccoliAppPluginInstance, tree]);
190+
const cssBlocksContentsTree = new CSSBlocksStylesPreprocessorPlugin(env.modulePrefix, env.config, [this.broccoliAppPluginInstance, tree]);
186191
return new BroccoliDebug(mergeTrees([tree, cssBlocksContentsTree], { overwrite: true }), "css-blocks:css-preprocess");
187192
} else {
188193
return tree;
189194
}
190195
},
196+
197+
postprocessTree(type, tree) {
198+
let env = this.env!;
199+
200+
if (type === "css") {
201+
if (!env.isApp) {
202+
return tree;
203+
}
204+
// TODO: THIS IS WRONG - FIX THESE HARCODED PATHS THIS IS A BLOCKER AAAUGH
205+
const concatTree = broccoliConcat(
206+
tree,
207+
{
208+
inputFiles: ["assets/css-blocks.css", "assets/@css-blocks-fixtures-v2/ember-app.css"],
209+
outputFile: "assets/@css-blocks-fixtures-v2/ember-app.css",
210+
sourceMapConfig: {
211+
enabled: true,
212+
extensions: ["css"],
213+
inline: true,
214+
mapCommentType: "block",
215+
},
216+
},
217+
);
218+
const mergedTree = mergeTrees([tree, concatTree], { overwrite: true });
219+
return new BroccoliDebug(mergedTree, "css-blocks:css-postprocess");
220+
}
221+
222+
return tree;
223+
},
191224
};
192225

193226
type MaybeCSSBlocksTree = MaybeCSSBlocksTreePlugin | string;

‎yarn.lock

+42-293
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.