-
Notifications
You must be signed in to change notification settings - Fork 413
/
4-extract.ts
148 lines (124 loc) · 3.78 KB
/
4-extract.ts
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
import path from 'path';
import type { Mapping } from 'source-map';
import { SourceMapGenerator } from 'source-map';
import stylis from 'stylis';
import type { BaseProcessor, Replacements } from '@linaria/tags';
import type { Rules, Options, PreprocessorFn } from '../types';
const STYLIS_DECLARATION = 1;
const posixSep = path.posix.sep;
export function transformUrl(
url: string,
outputFilename: string,
sourceFilename: string,
platformPath: typeof path = path
) {
// Replace asset path with new path relative to the output CSS
const relative = platformPath.relative(
platformPath.dirname(outputFilename),
// Get the absolute path to the asset from the path relative to the JS file
platformPath.resolve(platformPath.dirname(sourceFilename), url)
);
if (platformPath.sep === posixSep) {
return relative;
}
return relative.split(platformPath.sep).join(posixSep);
}
function extractCssFromAst(
rules: Rules,
originalCode: string,
options: Options
) {
const mappings: Mapping[] = [];
let cssText = '';
let preprocessor: PreprocessorFn;
if (typeof options.preprocessor === 'function') {
// eslint-disable-next-line prefer-destructuring
preprocessor = options.preprocessor;
} else {
switch (options.preprocessor) {
case 'none':
preprocessor = (selector, text) => `${selector} {${text}}\n`;
break;
case 'stylis':
default:
stylis.use(null)((context, decl) => {
const { outputFilename } = options;
if (context === STYLIS_DECLARATION && outputFilename) {
// When writing to a file, we need to adjust the relative paths inside url(..) expressions
// It'll allow css-loader to resolve an imported asset properly
return decl.replace(
/\b(url\((["']?))(\.[^)]+?)(\2\))/g,
(match, p1, p2, p3, p4) =>
p1 + transformUrl(p3, outputFilename, options.filename) + p4
);
}
return decl;
});
preprocessor = stylis;
}
}
Object.keys(rules).forEach((selector, index) => {
mappings.push({
generated: {
line: index + 1,
column: 0,
},
original: rules[selector].start!,
name: selector,
source: '',
});
if (rules[selector].atom) {
// For atoms, we just directly insert cssText, to give the atomizer full control over the rules
cssText += `${rules[selector].cssText}\n`;
} else {
// Run each rule through stylis to support nesting
cssText += `${preprocessor(selector, rules[selector].cssText)}\n`;
}
});
return {
cssText,
rules,
get cssSourceMapText() {
if (mappings?.length) {
const generator = new SourceMapGenerator({
file: options.filename.replace(/\.js$/, '.css'),
});
mappings.forEach((mapping) =>
generator.addMapping({ ...mapping, source: options.filename })
);
generator.setSourceContent(options.filename, originalCode);
return generator.toString();
}
return '';
},
};
}
/**
* Extract artifacts (e.g. CSS) from processors
*/
export default function extractStage(
processors: BaseProcessor[],
originalCode: string,
options: Options
) {
let allRules: Rules = {};
const allReplacements: Replacements = [];
processors.forEach((processor) => {
processor.artifacts.forEach((artifact) => {
if (artifact[0] !== 'css') return;
const [rules, replacements] = artifact[1] as [
rules: Rules,
sourceMapReplacements: Replacements
];
allRules = {
...allRules,
...rules,
};
allReplacements.push(...replacements);
});
});
return {
...extractCssFromAst(allRules, originalCode, options),
replacements: allReplacements,
};
}