1
- import * as fs from "fs" ;
1
+ import * as fs from "fs-extra " ;
2
2
import * as path from "path" ;
3
- import { promisify } from "util" ;
4
3
5
4
import { TemplateTypes } from "@opticss/template-api" ;
6
- import { Analyzer } from "css-blocks" ;
5
+ import { Analyzer , BlockCompiler , StyleMapping } from "css-blocks" ;
6
+ import { Optimizer } from "opticss" ;
7
+ import * as postcss from "postcss" ;
8
+ import * as readdir from "recursive-readdir" ;
7
9
8
10
import { BroccoliPlugin } from "./utils" ;
9
11
10
- const readdirAsync = promisify ( fs . readdirSync ) as ( path : string ) => Promise < string [ ] > ;
11
- const symlinkAsync = promisify ( fs . symlinkSync ) as ( from : string , to : string ) => Promise < void > ;
12
-
13
12
interface BroccoliOptions {
14
13
entry : string [ ] ;
14
+ output : string ;
15
15
analyzer : Analyzer < keyof TemplateTypes > ;
16
+ transport : { [ key : string ] : object } ;
16
17
}
17
18
18
19
class BroccoliCSSBlocks extends BroccoliPlugin {
19
20
20
21
private analyzer : Analyzer < keyof TemplateTypes > ;
21
22
private entry : string [ ] ;
23
+ private output : string ;
24
+ private transport : { [ key : string ] : object } ;
25
+ private optimizationOptions : object = { } ;
22
26
23
27
// tslint:disable-next-line:prefer-whatever-to-any
24
28
constructor ( inputNode : any , options : BroccoliOptions ) {
25
- super ( [ inputNode ] , {
26
- name : "broccoli-css-blocks" ,
27
- } ) ;
28
- this . analyzer = options . analyzer ;
29
+ super ( [ inputNode ] , { name : "broccoli-css-blocks" } ) ;
30
+
29
31
this . entry = options . entry ;
32
+ this . output = options . output ;
33
+ this . analyzer = options . analyzer ;
34
+ this . transport = options . transport ;
35
+
36
+ if ( ! this . output ) {
37
+ throw new Error ( "CSS Blocks Broccoli Plugin requires an output file name." ) ;
38
+ }
30
39
}
31
40
32
41
async build ( ) {
42
+ let options = this . analyzer . cssBlocksOptions ;
43
+ let blockCompiler = new BlockCompiler ( postcss , options ) ;
44
+ let optimizer = new Optimizer ( this . optimizationOptions , this . analyzer . optimizationOptions ) ;
33
45
34
- // This build step is just a pass-through of all files!
35
- // We're just analyzing right now.
36
- let files = await readdirAsync ( this . inputPaths [ 0 ] ) ;
46
+ // This build step is *mostly* just a pass-through of all files!
47
+ // QUESTION: Tom, is there a better way to do this in Broccoli?
48
+ let files = await readdir ( this . inputPaths [ 0 ] ) ;
37
49
for ( let file of files ) {
50
+ file = path . relative ( this . inputPaths [ 0 ] , file ) ;
51
+ await fs . ensureDir ( path . join ( this . outputPath , path . dirname ( file ) ) ) ;
38
52
try {
39
- await symlinkAsync (
53
+ await fs . symlink (
40
54
path . join ( this . inputPaths [ 0 ] , file ) ,
41
55
path . join ( this . outputPath , file ) ,
42
56
) ;
@@ -48,12 +62,50 @@ class BroccoliCSSBlocks extends BroccoliPlugin {
48
62
// Oh hey look, we're analyzing.
49
63
await this . analyzer . analyze ( ...this . entry ) ;
50
64
51
- // Here we'd compile the blocks, optionally optimize our output,
52
- // and inject the final CSS file into the tree. Then, attach our
53
- // StyleMapping data to whatever shared memory data transport we
54
- // have to pass to funnel rewrite data to our Rewriter.
65
+ // Compile all Blocks and add them as sources to the Optimizer.
66
+ // TODO: handle a sourcemap from compiling the block file via a preprocessor.
67
+ let blocks = this . analyzer . transitiveBlockDependencies ( ) ;
68
+ for ( let block of blocks ) {
69
+ if ( block . stylesheet ) {
70
+ let root = blockCompiler . compile ( block , block . stylesheet , this . analyzer ) ;
71
+ let result = root . toResult ( { to : this . output , map : { inline : false , annotation : false } } ) ;
72
+ let filesystemPath = options . importer . filesystemPath ( block . identifier , options ) ;
73
+ let filename = filesystemPath || options . importer . debugIdentifier ( block . identifier , options ) ;
74
+
75
+ // If this Block has a representation on disk, remove it from our output tree.
76
+ // TODO: This isn't working right now because `importer.filesystemPath` doesn't return the expected path...
77
+ if ( filesystemPath ) {
78
+ await fs . remove ( path . join ( this . outputPath , path . relative ( options . rootDir , filesystemPath ) ) ) ;
79
+ }
80
+
81
+ // Add the compiled Block file to the optimizer.
82
+ optimizer . addSource ( {
83
+ content : result . css ,
84
+ filename,
85
+ sourceMap : result . map . toJSON ( ) ,
86
+ } ) ;
87
+ }
88
+ }
89
+
90
+ // Add each Analysis to the Optimizer.
91
+ this . analyzer . eachAnalysis ( ( a ) => optimizer . addAnalysis ( a . forOptimizer ( options ) ) ) ;
92
+
93
+ // Run optimization and compute StyleMapping.
94
+ let optimized = await optimizer . optimize ( this . output ) ;
95
+ let styleMapping = new StyleMapping < "Opticss.Template" > ( optimized . styleMapping , blocks , options , this . analyzer . analyses ( ) ) ;
96
+
97
+ // Attach all computed data to our magic shared memory transport object...
98
+ this . transport . mapping = styleMapping ;
99
+ this . transport . blocks = blocks ;
100
+ this . transport . analyzer = this . analyzer ;
101
+ this . transport . css = optimized . output ;
55
102
56
- return this . analyzer ;
103
+ // Write our compiled CSS to the output tree.
104
+ // QUESTION: GUH! TOM! THIS DOESN'T APPEAR IN THE OUTPUT TREE!
105
+ await fs . outputFile (
106
+ path . join ( this . outputPath , this . output ) ,
107
+ optimized . output . content . toString ( ) ,
108
+ ) ;
57
109
58
110
}
59
111
0 commit comments