-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathindex.js
156 lines (136 loc) · 5.31 KB
/
index.js
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
const path = require('path');
const compiler = require('vue-template-compiler');
const loaderUtils = require('loader-utils');
const _ = require('lodash');
// Given a key-value object of attributes, compose the attribute potion of the
// Vue Single File Component tag
//
// Example:
// genAttrs({ foo: 'bar', baz: 'qux' })
//
// => ' foo="bar" baz="qux"'
function genAttrs(attrs) {
return _.reduce(attrs, (acc, v, k) => {
let attr = ` ${k}`;
if (v !== true) {
attr += `="${v}"`;
}
return acc + attr;
}, '');
}
// Given a tag and a vue-template-compiler section, re-generate the Single File
// Component structure
//
// Example:
// genSection('template', {
// attrs: {
// foo: 'bar'
// },
// content: '<h1>Hello World!</h1>'
// })
//
// => '<template foo="bar"><h1>Hello World</h1></template>'
function genSection(tag, section) {
if (!section) {
return '';
}
const attrs = genAttrs(section.attrs);
const content = section.content || '';
return `<${tag}${attrs}>${content}</${tag}>\n`;
}
// Replace the contents for a given style block with blank lines so that
// line numbers remain the same as the input file
//
// Example:
// replaceWithSpacer({
// content: '.class-name {\n color: red;\n font-weight: bold;\n}\n'
// })
//
// => {
// content: '\n\n\n\n'
// }
function replaceWithSpacer(style) {
const lines = style.content.split('\n').length;
const spacer = new Array(lines).join('\n');
_.set(style, 'content', spacer);
}
// Determine if a given <style> section should be replaced, based on looking
// at all other sections. Rules for replacement:
// - Sections will only ever be replaced by sibling sections that are of the
// same "scoped" value (i.e., non-scoped or scoped)
// - All themed sections will be replaced if they are not the current theme
// - A themed section with a boolean `replace` attribute will replace _all_
// non-themed sections for the same scoped value
// - A themed section that passes a value to the `replace` attribute will
// only replace non-themed sections with `id="..."` where the value matches
// the value of the `replace` attribute
function shouldReplaceCurrentSection(style, styles, options) {
const { attrs } = style;
const sectionScoped = _.get(attrs, 'scoped') === true;
const sectionTheme = _.get(attrs, 'theme');
const isThemed = sectionTheme != null && sectionTheme.length > 0;
const sectionId = _.get(attrs, 'id');
// Replace all non-active themed sections
if (isThemed) {
return sectionTheme !== options.theme;
}
// This is a base section, see if we find a themed section to replace us
const replacementStyle = styles
// Only look at subsequent active themes
.filter(s => _.get(s.attrs, 'theme') === options.theme)
// Of the same scope
.filter(s => ((_.get(s.attrs, 'scoped') === true) === sectionScoped))
.find(s => (
// When we find a boolean replace attribute, we always replace
_.get(s.attrs, 'replace') === true ||
// Or we replace if we find a themed section that specified to
// replace our specific id
(sectionId != null && _.get(s.attrs, 'replace') === sectionId)
));
return replacementStyle != null;
}
// Given a style section from vue-template-compiler, generate the resulting
// output style section, stripping inactive theme style blocks
function genStyleSection(style, styles, options) {
if (shouldReplaceCurrentSection(style, styles, options)) {
replaceWithSpacer(style);
}
// Remove the 'id', theme' and 'replace' attributes from the output set of
// attributes since they're not really valid SFC <style> attributes
_.set(style, 'attrs', _.omit(style.attrs, 'id', 'theme', 'replace'));
return genSection('style', style);
}
function vueThemedStyleLoader(source) {
// Grab options passed in via webpack config
const options = _.defaults(loaderUtils.getOptions(this) || {}, {
theme: null,
debug: false,
});
// Parse the Vue Single File Component
const parts = compiler.parseComponent(source);
// Generate the singular <template>/<script> sections
const template = genSection('template', parts.template);
const script = genSection('script', parts.script);
// Loop over style sections, eliminating inactive brands
const styles = parts.styles
.map(s => genStyleSection(s, parts.styles, options))
.join('\n');
let output = '';
// Maintain any custom blocks at the top of the file
// This has an assumption now that they are all above <template>
if (_.isArray(parts.customBlocks)) {
output += parts.customBlocks.reduce((acc, b) =>
`${acc}<${b.type}>${b.content}</${b.type}>\n\n`, '');
}
// Reconstruct the Vue Single File Component
output += `${template}\n${script}\n${styles}`;
if (options.debug) {
const filePath = this.resourcePath;
const fileName = path.basename(filePath);
console.log(`---------- Begin ${fileName} ----------`);
console.log(output);
console.log(`---------- End ${fileName} ----------`);
}
return output;
}
module.exports = vueThemedStyleLoader;