-
Notifications
You must be signed in to change notification settings - Fork 1
/
plugin.coffee
188 lines (141 loc) · 5.77 KB
/
plugin.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
### Wintersmith Sassify Plugin ###
# Set default options for Node Sass
# https://github.com/sass/node-sass
defaults =
fileGlob: '**/*.s[ac]ss'
includePaths: []
indentedSyntax: false
indentType: 'space'
indentWidth: 2
linefeed: 'lf'
omitSourceMapUrl: false
outputStyle: 'nested'
precision: 5
sourceComments: false
sourceMap: undefined
sourceMapContents: false
sourceMapEmbed: false
sourceMapRoot: undefined
# Require dependencies
fs = require 'fs'
nodeSass = require 'node-sass'
# The extend in Wintersmith env.utils only accepts two arguments at a time so...
extend = (base = {}, objects...) ->
for i, object of objects
for prop, val of object
base[prop] = val
base
# This config var will become a shorthand for env.config['sassify']
config = {}
module.exports = (env, callback) ->
# The final environment configuration is derived from extending the current
# plugin configuration from the Wintersmith environment with the plugin
# defaults. At render time the configuration passed to Node Sass will be
# derived by extending the environment configuration with the plugin
# instance's configuration property.
env.config['sassify'] = config = extend {}, defaults, env.config['sassify']
# The Sass plugin manages the Sass file itself. If the Sass file is to output
# CSS or a source map that is handled by the other respective plugins.
class Sass extends env.ContentPlugin
# Note that all files with a .sass extension will be rendered using indented
# syntax regardless of the configuration setting.
constructor: (@filePath, fileContents) ->
@config =
file: @filePath.full
indentedSyntax: if /\.sass$/.test(@filePath.relative) then true else config.indentedSyntax
unless do @isPartial
@css = new CSS @
extend @config,
outFile: do @css.getFilename
if do @hasSourceMap
@sourceMap = new SourceMap @
getConfig: ->
extend {}, config, @config
getFilename: ->
@filePath.relative
getView: ->
(env, locals, contents, templates, callback) ->
try
stream = fs.createReadStream @filePath.full
catch err
return callback err
callback null, stream
hasSourceMap: ->
sassConfig = do @getConfig
not do @isPartial and sassConfig.sourceMap and not sassConfig.sourceMapEmbed
isPartial: ->
/^_/.test @filePath.name
# Any CSS or source maps derived from this file will use this method to
# render their output.
render: (output, callback) ->
nodeSass.render do @getConfig, (err, result) =>
return callback err if err or not result?[output]
callback null, result[output]
# The CSS plugin manages any CSS that is to be output by a Sass file.
class CSS extends env.ContentPlugin
constructor: (@sassFile) ->
getFilename: ->
@sassFile.filePath.relative.replace /\.[a-z0-9]+$/i, '.css'
getUrl: ->
super env.config.baseUrl
getView: ->
(env, locals, contents, templates, callback) ->
@sassFile.render 'css', callback
# The SourceMap plugin manages any source maps to be output by a Sass file.
class SourceMap extends env.ContentPlugin
constructor: (@sassFile) ->
sourceMap = @sassFile.getConfig().sourceMap
@customName = if sourceMap and sourceMap.length then sourceMap else false
getFilename: ->
@customName or @sassFile.filePath.relative.replace /\.[a-z0-9]+$/i, '.css.map'
getUrl: ->
@customName or super env.config.baseUrl
getView: ->
(env, locals, contents, templates, callback) ->
@sassFile.render 'map', (err, result) =>
return callback err if err or not result
@resolveSourcePaths result, callback
# Source paths from Node Sass will be file paths rather than URLs so they
# need to be resolved to relative URLs.
resolveSourcePaths: (result, callback) ->
contentsPath = new RegExp "\"([\.\/]*)?#{env.config.contents.substr 2}\/", 'g'
map = result.toString().replace contentsPath, '"$1'
callback null, Buffer.from map
# This content tree will contain the "virtual" CSS and source maps generated
# by our Sass files and registered as a generator with Wintermsith. This
# "virtual" content tree will be merged with Wintermsith with the "real"
# content tree that is generated from the Wintersmith contents directory.
contentTree = new class ContentTree
constructor: ->
@tree = {}
add: (content) ->
pointer = @tree
components = content.getUrl().replace(/^\//, '').split '/'
for component, index in components
if index < (components.length - 1)
pointer[component] ?= {}
pointer = pointer[component]
else
pointer[component] = content
get: (contents, callback) =>
callback null, @tree
# When scanning the file tree for Sass files a Sass plugin instance will be
# constructed first and then, if necessary, the CSS and/or SourceMap instances
# will be added to the plugin's content tree.
Sass.fromFile = (filePath, callback) ->
filePath.name = /[^\\\/\.]*\.[a-z0-9]+$/i.exec(filePath.relative)[0]
if config.includePaths.indexOf(folder = filePath.full.replace filePath.name, '') < 0
config.includePaths.push folder
fs.readFile filePath.full, (err, fileContents) ->
return callback err if err
sass = new Sass filePath, fileContents
unless do sass.isPartial
contentTree.add sass.css
if do sass.hasSourceMap
contentTree.add sass.sourceMap
callback null, sass
# The glob pattern used to register the plugin is configurable and defaults
# to all .scss and .sass files.
env.registerContentPlugin 'styles', config.fileGlob, Sass
env.registerGenerator 'styles', contentTree.get
do callback