-
Notifications
You must be signed in to change notification settings - Fork 152
Flesh out broccoli-css-blocks
plugin.
#87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,42 +1,56 @@ | ||
import * as fs from "fs"; | ||
import * as fs from "fs-extra"; | ||
import * as path from "path"; | ||
import { promisify } from "util"; | ||
|
||
import { TemplateTypes } from "@opticss/template-api"; | ||
import { Analyzer } from "css-blocks"; | ||
import { Analyzer, BlockCompiler, StyleMapping } from "css-blocks"; | ||
import { Optimizer } from "opticss"; | ||
import * as postcss from "postcss"; | ||
import * as readdir from "recursive-readdir"; | ||
|
||
import { BroccoliPlugin } from "./utils"; | ||
|
||
const readdirAsync = promisify(fs.readdirSync) as (path: string) => Promise<string[]>; | ||
const symlinkAsync = promisify(fs.symlinkSync) as (from: string, to: string) => Promise<void>; | ||
|
||
interface BroccoliOptions { | ||
entry: string[]; | ||
output: string; | ||
analyzer: Analyzer<keyof TemplateTypes>; | ||
transport: {[key: string]: object}; | ||
} | ||
|
||
class BroccoliCSSBlocks extends BroccoliPlugin { | ||
|
||
private analyzer: Analyzer<keyof TemplateTypes>; | ||
private entry: string[]; | ||
private output: string; | ||
private transport: { [key: string]: object }; | ||
private optimizationOptions: object = {}; | ||
|
||
// tslint:disable-next-line:prefer-whatever-to-any | ||
constructor(inputNode: any, options: BroccoliOptions) { | ||
super([inputNode], { | ||
name: "broccoli-css-blocks", | ||
}); | ||
this.analyzer = options.analyzer; | ||
super([inputNode], { name: "broccoli-css-blocks" }); | ||
|
||
this.entry = options.entry; | ||
this.output = options.output; | ||
this.analyzer = options.analyzer; | ||
this.transport = options.transport; | ||
|
||
if (!this.output) { | ||
throw new Error("CSS Blocks Broccoli Plugin requires an output file name."); | ||
} | ||
} | ||
|
||
async build() { | ||
let options = this.analyzer.cssBlocksOptions; | ||
let blockCompiler = new BlockCompiler(postcss, options); | ||
let optimizer = new Optimizer(this.optimizationOptions, this.analyzer.optimizationOptions); | ||
|
||
// This build step is just a pass-through of all files! | ||
// We're just analyzing right now. | ||
let files = await readdirAsync(this.inputPaths[0]); | ||
// This build step is *mostly* just a pass-through of all files! | ||
// QUESTION: Tom, is there a better way to do this in Broccoli? | ||
let files = await readdir(this.inputPaths[0]); | ||
for (let file of files) { | ||
file = path.relative(this.inputPaths[0], file); | ||
await fs.ensureDir(path.join(this.outputPath, path.dirname(file))); | ||
try { | ||
await symlinkAsync( | ||
await fs.symlink( | ||
path.join(this.inputPaths[0], file), | ||
path.join(this.outputPath, file), | ||
); | ||
|
@@ -48,12 +62,50 @@ class BroccoliCSSBlocks extends BroccoliPlugin { | |
// Oh hey look, we're analyzing. | ||
await this.analyzer.analyze(...this.entry); | ||
|
||
// Here we'd compile the blocks, optionally optimize our output, | ||
// and inject the final CSS file into the tree. Then, attach our | ||
// StyleMapping data to whatever shared memory data transport we | ||
// have to pass to funnel rewrite data to our Rewriter. | ||
// Compile all Blocks and add them as sources to the Optimizer. | ||
// TODO: handle a sourcemap from compiling the block file via a preprocessor. | ||
let blocks = this.analyzer.transitiveBlockDependencies(); | ||
for (let block of blocks) { | ||
if (block.stylesheet) { | ||
let root = blockCompiler.compile(block, block.stylesheet, this.analyzer); | ||
let result = root.toResult({ to: this.output, map: { inline: false, annotation: false } }); | ||
let filesystemPath = options.importer.filesystemPath(block.identifier, options); | ||
let filename = filesystemPath || options.importer.debugIdentifier(block.identifier, options); | ||
|
||
// If this Block has a representation on disk, remove it from our output tree. | ||
// TODO: This isn't working right now because `importer.filesystemPath` doesn't return the expected path... | ||
if (filesystemPath) { | ||
await fs.remove(path.join(this.outputPath, path.relative(options.rootDir, filesystemPath))); | ||
} | ||
|
||
// Add the compiled Block file to the optimizer. | ||
optimizer.addSource({ | ||
content: result.css, | ||
filename, | ||
sourceMap: result.map.toJSON(), | ||
}); | ||
} | ||
} | ||
|
||
// Add each Analysis to the Optimizer. | ||
this.analyzer.eachAnalysis((a) => optimizer.addAnalysis(a.forOptimizer(options))); | ||
|
||
// Run optimization and compute StyleMapping. | ||
let optimized = await optimizer.optimize(this.output); | ||
let styleMapping = new StyleMapping<"Opticss.Template">(optimized.styleMapping, blocks, options, this.analyzer.analyses()); | ||
|
||
// Attach all computed data to our magic shared memory transport object... | ||
this.transport.mapping = styleMapping; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is all hand-wave-y shared state between Rewriters and Analyzers. Chris and I are still settling on a proper way to handle this for all build types – this is in a "just get it to work" state and will be cleaned up before landing. |
||
this.transport.blocks = blocks; | ||
this.transport.analyzer = this.analyzer; | ||
this.transport.css = optimized.output; | ||
|
||
return this.analyzer; | ||
// Write our compiled CSS to the output tree. | ||
// QUESTION: GUH! TOM! THIS DOESN'T APPEAR IN THE OUTPUT TREE! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the biggest problem that I don't quite understand 🙂 |
||
await fs.outputFile( | ||
path.join(this.outputPath, this.output), | ||
optimized.output.content.toString(), | ||
); | ||
|
||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -364,6 +364,12 @@ | |
version "1.10.0" | ||
resolved "https://registry.npmjs.org/@types/prettier/-/prettier-1.10.0.tgz#5abf1ec0a6e904fe2490cc2068f36a38e4a63c42" | ||
|
||
"@types/recursive-readdir@^2.2.0": | ||
version "2.2.0" | ||
resolved "https://registry.npmjs.org/@types/recursive-readdir/-/recursive-readdir-2.2.0.tgz#b39cd5474fd58ea727fe434d5c68b7a20ba9121c" | ||
dependencies: | ||
"@types/node" "*" | ||
|
||
"@types/shelljs@^0.7.0": | ||
version "0.7.8" | ||
resolved "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.7.8.tgz#4b4d6ee7926e58d7bca448a50ba442fd9f6715bd" | ||
|
@@ -2284,6 +2290,14 @@ fs-extra@^4.0.1: | |
jsonfile "^4.0.0" | ||
universalify "^0.1.0" | ||
|
||
fs-extra@^5.0.0: | ||
version "5.0.0" | ||
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" | ||
dependencies: | ||
graceful-fs "^4.1.2" | ||
jsonfile "^4.0.0" | ||
universalify "^0.1.0" | ||
|
||
fs-tree-diff@^0.5.3: | ||
version "0.5.7" | ||
resolved "https://registry.npmjs.org/fs-tree-diff/-/fs-tree-diff-0.5.7.tgz#315e2b098d5fe7f622880ac965b1b051868ac871" | ||
|
@@ -3676,7 +3690,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: | |
version "1.0.1" | ||
resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" | ||
|
||
"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: | ||
"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: | ||
version "3.0.4" | ||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" | ||
dependencies: | ||
|
@@ -4495,7 +4509,7 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 | |
source-map "^0.5.6" | ||
supports-color "^3.2.3" | ||
|
||
postcss@^6.0.1, postcss@^6.0.12, postcss@^6.0.14, postcss@^6.0.19: | ||
postcss@^6.0.1, postcss@^6.0.12, postcss@^6.0.14: | ||
version "6.0.19" | ||
resolved "https://registry.npmjs.org/postcss/-/postcss-6.0.19.tgz#76a78386f670b9d9494a655bf23ac012effd1555" | ||
dependencies: | ||
|
@@ -4747,6 +4761,12 @@ rechoir@^0.6.2: | |
dependencies: | ||
resolve "^1.1.6" | ||
|
||
recursive-readdir@^2.2.2: | ||
version "2.2.2" | ||
resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" | ||
dependencies: | ||
minimatch "3.0.4" | ||
|
||
redent@^1.0.0: | ||
version "1.0.0" | ||
resolved "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" | ||
|
@@ -5725,9 +5745,9 @@ [email protected]: | |
version "2.3.4" | ||
resolved "https://registry.npmjs.org/typescript/-/typescript-2.3.4.tgz#3d38321828231e434f287514959c37a82b629f42" | ||
|
||
typescript@^2.7.2: | ||
version "2.7.2" | ||
resolved "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836" | ||
typescript@^2.8.1: | ||
version "2.8.1" | ||
resolved "https://registry.npmjs.org/typescript/-/typescript-2.8.1.tgz#6160e4f8f195d5ba81d4876f9c0cc1fbc0820624" | ||
|
||
uglify-js@^2.6, uglify-js@^2.8.29: | ||
version "2.8.29" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aka, "hey broccoli, recursively symlink every file for me, but make sure the directories are all real so I'm not editing someone else's output tree by accident – or worse, the source files!