UNPKG

5.12 kBJavaScriptView Raw
1"use strict";
2
3const Plugin = require('broccoli-plugin');
4const fs = require('fs');
5const path = require('path');
6const walkSync = require('walk-sync');
7const getModuleConfig = require('./get-module-config');
8const getModuleSpecifier = require('./get-module-specifier');
9const SilentError = require('silent-error');
10
11const IGNORED_EXTENSIONS = ['', '.md', '.html'];
12const IGNORED_SUFFIXES = ['.d.ts'];
13const IGNORED_PREFIXES = ['.'];
14
15const INTERFACE = `
16export interface Dict<T> {
17 [index: string]: T;
18}
19declare let map: Dict<any>;
20export default map;
21`;
22
23function getModuleIdentifier(seen, modulePath) {
24 let identifier = modulePath
25 // replace any non letter, non-number, non-underscore
26 .replace(/[\W]/g, '_');
27
28 // if we have already generated this identifier
29 // prefix with an _ until we find a unique one
30 while (seen[identifier]) {
31 identifier = `_${identifier}`;
32 }
33
34 seen[identifier] = modulePath;
35
36 return `__${identifier}__`;
37}
38
39function shouldIgnore(pathParts) {
40 // ignore any of these extensions (.md, .html, etc)
41 if (IGNORED_EXTENSIONS.indexOf(pathParts.ext) > -1) {
42 return true;
43 }
44
45 // ignore .d.ts files, etc
46 if (IGNORED_SUFFIXES.some(suffix => pathParts.base.endsWith(suffix))) {
47 return true;
48 }
49
50 // ignore dotfiles
51 if (IGNORED_PREFIXES.some(prefix => pathParts.base.startsWith(prefix))) {
52 return true;
53 }
54
55 return false;
56}
57
58function ResolutionMapBuilder(src, config, options) {
59 options = options || {};
60 Plugin.call(this, [src, config], {
61 annotation: options.annotation
62 });
63 this.options = options;
64}
65
66ResolutionMapBuilder.prototype = Object.create(Plugin.prototype);
67
68ResolutionMapBuilder.prototype.constructor = ResolutionMapBuilder;
69
70ResolutionMapBuilder.prototype.build = function() {
71 // Attempt to read config file
72 let configPath = path.posix.join(this.inputPaths[1], this.options.configPath);
73 let config;
74 if (fs.existsSync(configPath)) {
75 let configContents = fs.readFileSync(configPath, { encoding: 'utf8' });
76 config = JSON.parse(configContents);
77 } else {
78 config = {};
79 }
80
81 let moduleConfig = getModuleConfig(config.moduleConfiguration || this.options.defaultModuleConfiguration);
82 if (!moduleConfig) {
83 throw new Error(`The module configuration could not be found. Please add a config file to '${configPath}' and export an object with a 'moduleConfiguration' member.`);
84 }
85
86 let modulePrefix = config.modulePrefix || this.options.defaultModulePrefix;
87 if (!modulePrefix) {
88 throw new Error(`The module prefix could not be found. Add a config file to '${configPath}' and export an object with a 'modulePrefix' member.`);
89 }
90
91 let baseDir = this.options.baseDir || '';
92 let modulePath = path.posix.join(this.inputPaths[0], baseDir);
93 let modulePaths = walkSync(modulePath);
94 let mappedPaths = [];
95 let moduleImports = [];
96 let mapContents = [];
97
98 modulePaths.forEach(function(modulePath) {
99 let pathParts = path.parse(modulePath);
100
101 if (shouldIgnore(pathParts)) {
102 return;
103 }
104
105 let name = path.posix.join(pathParts.dir, pathParts.name);
106
107 // filter out index module
108 if (name !== 'index' && name !== 'main') {
109 mappedPaths.push(modulePath);
110 }
111 });
112
113 if (this.options.logSpecifiers) {
114 this.specifiers = [];
115 }
116
117 let seenSpecifiers = Object.create(null);
118 let seenModuleVars = Object.create(null);
119
120 mappedPaths.forEach(modulePath => {
121 let pathParts = path.parse(modulePath);
122 let module = path.posix.join(pathParts.dir, pathParts.name);
123 let extension = pathParts.ext;
124 let specifier = getModuleSpecifier(modulePrefix, moduleConfig, module, extension);
125
126 // Only process non-null specifiers returned.
127 // Specifiers may be null in the case of an unresolvable collection (e.g. utils)
128 if (specifier) {
129 if (seenSpecifiers[specifier]) {
130 throw new SilentError(`Both \`${seenSpecifiers[specifier]}\` and \`${modulePath}\` represent ${specifier}, please rename one to remove the collision.`);
131 } else {
132 seenSpecifiers[specifier] = modulePath;
133 }
134
135 let moduleImportPath = path.posix.join('..', baseDir, module);
136 let moduleVar = getModuleIdentifier(seenModuleVars, module);
137 let moduleImport = "import { default as " + moduleVar + " } from '" + moduleImportPath + "';";
138 moduleImports.push(moduleImport);
139 mapContents.push("'" + specifier + "': " + moduleVar);
140
141 if (this.options.logSpecifiers) {
142 this.specifiers.push(specifier);
143 }
144 }
145 });
146
147 let destPath = path.posix.join(this.outputPath, 'config');
148 if (!fs.existsSync(destPath)) {
149 fs.mkdirSync(destPath);
150 }
151
152 let contents = moduleImports.join('\n') + '\n' +
153 "export default {" + mapContents.join(',') + "};" + '\n';
154
155 fs.writeFileSync(path.posix.join(this.outputPath, 'config', 'module-map.js'), contents, { encoding: 'utf8' });
156 fs.writeFileSync(path.posix.join(this.outputPath, 'config', 'module-map.d.ts'), INTERFACE, { encoding: 'utf8' });
157};
158
159module.exports = ResolutionMapBuilder;
160module.exports._getModuleIdentifier = getModuleIdentifier;
161module.exports._shouldIgnore = shouldIgnore;