1 | "use strict";
|
2 |
|
3 | var _path = require("path");
|
4 |
|
5 | var _util = _interopRequireDefault(require("util"));
|
6 |
|
7 | var _chalk = _interopRequireDefault(require("chalk"));
|
8 |
|
9 | var _fastLevenshtein = _interopRequireDefault(require("fast-levenshtein"));
|
10 |
|
11 | var _loaderUtils = _interopRequireDefault(require("loader-utils"));
|
12 |
|
13 | var _sortBy = _interopRequireDefault(require("lodash/sortBy"));
|
14 |
|
15 | var _codeFrame = require("@babel/code-frame");
|
16 |
|
17 | var _traverse = _interopRequireDefault(require("./traverse"));
|
18 |
|
19 | var _createFilename = require("./utils/createFilename");
|
20 |
|
21 | var _VirtualModulePlugin = _interopRequireDefault(require("./VirtualModulePlugin"));
|
22 |
|
23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
24 |
|
25 | const debug = _util.default.debuglog('astroturf:loader');
|
26 |
|
27 |
|
28 | function AstroturfLoaderError(errorOrMessage, codeFrame = errorOrMessage.codeFrame) {
|
29 | Error.call(this);
|
30 | this.name = 'AstroturfLoaderError';
|
31 |
|
32 | if (typeof errorOrMessage !== 'string') {
|
33 | this.message = errorOrMessage.message;
|
34 | this.error = errorOrMessage;
|
35 |
|
36 | try {
|
37 | this.stack = errorOrMessage.stack.replace(/^(.*?):/, `${this.name}:`);
|
38 | } catch (err) {
|
39 | Error.captureStackTrace(this, AstroturfLoaderError);
|
40 | }
|
41 | } else {
|
42 | this.message = errorOrMessage;
|
43 | Error.captureStackTrace(this, AstroturfLoaderError);
|
44 | }
|
45 |
|
46 | if (codeFrame) this.message += `\n\n${codeFrame}\n`;
|
47 | }
|
48 |
|
49 | AstroturfLoaderError.prototype = Object.create(Error.prototype);
|
50 | AstroturfLoaderError.prototype.constructor = AstroturfLoaderError;
|
51 |
|
52 | function buildDependencyError(content, {
|
53 | type,
|
54 | identifier,
|
55 | request
|
56 | }, {
|
57 | styles,
|
58 | resource
|
59 | }, loc) {
|
60 | let idents = styles.map(s => s.identifier);
|
61 | let closest;
|
62 | let minDistance = 2;
|
63 | idents.forEach(ident => {
|
64 | const d = _fastLevenshtein.default.get(ident, identifier);
|
65 |
|
66 | if (d < minDistance) {
|
67 | minDistance = d;
|
68 | closest = ident;
|
69 | }
|
70 | });
|
71 | const isDefaultImport = type === 'ImportDefaultSpecifier';
|
72 |
|
73 | if (!closest && isDefaultImport) {
|
74 | closest = idents.find(ident => ident === (0, _createFilename.getNameFromFile)(resource));
|
75 | }
|
76 |
|
77 | if (closest) idents = idents.filter(ident => ident !== closest);
|
78 | idents = idents.map(s => _chalk.default.yellow(s)).join(', ');
|
79 | const alternative = isDefaultImport ? `Instead try: ${_chalk.default.yellow(`import ${closest} from '${request}';`)}` : `Did you mean to import as ${_chalk.default.yellow(closest)} instead?`;
|
80 | return new AstroturfLoaderError(
|
81 | `Could not find a style associated with the interpolated value. ` + `Styles should use the same name used by the intended component or class set in the imported file.\n\n` + (0, _codeFrame.codeFrameColumns)(content, {
|
82 | start: loc.start
|
83 | }, {
|
84 | highlightCode: true,
|
85 | message: !isDefaultImport ? `(Imported as ${_chalk.default.bold(identifier)})` : ''
|
86 | }) + `\n\n${closest ? `${alternative}\n\nAlso available: ${idents}` : `Available: ${idents}`}`);
|
87 | }
|
88 |
|
89 | function collectStyles(src, filename, resolveDependency, opts) {
|
90 |
|
91 | try {
|
92 | const {
|
93 | metadata
|
94 | } = (0, _traverse.default)(src, filename, { ...opts,
|
95 | resolveDependency,
|
96 | writeFiles: false,
|
97 | generateInterpolations: true
|
98 | });
|
99 | return metadata.astroturf;
|
100 | } catch (err) {
|
101 | throw new AstroturfLoaderError(err);
|
102 | }
|
103 | }
|
104 |
|
105 | function replaceStyleTemplates(src, locations) {
|
106 | locations = (0, _sortBy.default)(locations, i => i.start || 0);
|
107 | let offset = 0;
|
108 |
|
109 | function splice(str, start = 0, end = 0, replace) {
|
110 | const result = str.slice(0, start + offset) + replace + str.slice(end + offset);
|
111 | offset += replace.length - (end - start);
|
112 | return result;
|
113 | }
|
114 |
|
115 | locations.forEach(({
|
116 | start,
|
117 | end,
|
118 | code
|
119 | }) => {
|
120 | if (code.endsWith(';')) code = code.slice(0, -1);
|
121 |
|
122 | src = splice(src, start, end, code);
|
123 | });
|
124 | return src;
|
125 | }
|
126 |
|
127 | const LOADER_PLUGIN = Symbol('loader added VM plugin');
|
128 | const SEEN = Symbol('astroturf seen modules');
|
129 |
|
130 | module.exports = function loader(content, map, meta) {
|
131 | const {
|
132 | resourcePath,
|
133 | _compilation: compilation
|
134 | } = this;
|
135 | const cb = this.async();
|
136 |
|
137 | const timeout = async (ms, promise, err) => {
|
138 | const handle = setTimeout(() => {
|
139 | this.emitWarning(err);
|
140 | }, ms);
|
141 |
|
142 | try {
|
143 | return await promise;
|
144 | } finally {
|
145 | clearTimeout(handle);
|
146 | }
|
147 | };
|
148 |
|
149 | if (!compilation[SEEN]) compilation[SEEN] = new Map();
|
150 |
|
151 | const loadModule = _util.default.promisify((request, done) => this.loadModule(request, (err, _, __, module) => done(err, module)));
|
152 |
|
153 | const resolve = _util.default.promisify(this.resolve);
|
154 |
|
155 | const buildDependency = async request => {
|
156 | const resource = await resolve((0, _path.dirname)(resourcePath), request);
|
157 | const maybeCycle = compilation[SEEN].has(resource);
|
158 |
|
159 |
|
160 |
|
161 | return maybeCycle ? timeout(10000, loadModule(resource), new AstroturfLoaderError('A possible cyclical style interpolation was detected in an interpolated stylesheet or component which is not supported.\n' + `while importing "${request}" in ${resourcePath}`)) : loadModule(resource);
|
162 | };
|
163 |
|
164 | const options = _loaderUtils.default.getOptions(this) || {};
|
165 | const dependencies = [];
|
166 |
|
167 | function resolveDependency(interpolation, localStyle, node) {
|
168 | const {
|
169 | identifier,
|
170 | request
|
171 | } = interpolation;
|
172 | if (!interpolation.identifier) return null;
|
173 | const {
|
174 | loc
|
175 | } = node;
|
176 | const memberProperty = node.property && node.property.name;
|
177 | const imported = `###ASTROTURF_IMPORTED_${dependencies.length}###`;
|
178 | const source = `###ASTROTURF_SOURCE_${dependencies.length}###`;
|
179 | debug(`resolving dependency: ${request}`);
|
180 | dependencies.push(buildDependency(request).then(module => {
|
181 | const style = module.styles.find(s => s.identifier === identifier);
|
182 |
|
183 | if (!style) {
|
184 | throw buildDependencyError(content, interpolation, module, loc);
|
185 | }
|
186 |
|
187 | debug(`resolved request to: ${style.absoluteFilePath}`);
|
188 | localStyle.value = localStyle.value.replace(source, `~${style.absoluteFilePath}`).replace(imported, style.isStyledComponent ? 'cls1' : memberProperty);
|
189 | }));
|
190 | return {
|
191 | source,
|
192 | imported
|
193 | };
|
194 | }
|
195 |
|
196 | const {
|
197 | styles = [],
|
198 | changeset
|
199 | } = collectStyles(content, resourcePath, resolveDependency, options);
|
200 |
|
201 | if (meta) {
|
202 | meta.styles = styles;
|
203 | }
|
204 |
|
205 | if (!styles.length) {
|
206 | return cb(null, content);
|
207 | }
|
208 |
|
209 | compilation[SEEN].set(resourcePath, styles);
|
210 | this._module.styles = styles;
|
211 | let {
|
212 | emitVirtualFile
|
213 | } = this;
|
214 |
|
215 | if (!emitVirtualFile) {
|
216 | const {
|
217 | compiler
|
218 | } = compilation;
|
219 | let plugin = compiler[LOADER_PLUGIN];
|
220 |
|
221 | if (!plugin) {
|
222 | debug('adding plugin to compiiler');
|
223 | plugin = _VirtualModulePlugin.default.bootstrap(compilation);
|
224 | compiler[LOADER_PLUGIN] = plugin;
|
225 | }
|
226 |
|
227 | emitVirtualFile = plugin.addFile;
|
228 | }
|
229 |
|
230 |
|
231 | return Promise.all(dependencies).then(() => {
|
232 | styles.forEach(style => {
|
233 | const mtime = emitVirtualFile(style.absoluteFilePath, style.value);
|
234 | compilation.fileTimestamps.set(style.absoluteFilePath, +mtime);
|
235 | });
|
236 | const result = replaceStyleTemplates(content, changeset);
|
237 | cb(null, result);
|
238 | }).catch(cb);
|
239 | }; |
\ | No newline at end of file |