This repository was archived by the owner on Apr 8, 2020. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathLoadViaWebpack.ts
More file actions
143 lines (130 loc) · 8.35 KB
/
Copy pathLoadViaWebpack.ts
File metadata and controls
143 lines (130 loc) · 8.35 KB
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
// When you're using Webpack, it's often convenient to be able to require modules from regular JavaScript
// and have them transformed by Webpack. This is especially useful when doing ASP.NET server-side prerendering,
// because it means your boot module can use whatever source language you like (e.g., TypeScript), and means
// that your loader plugins (e.g., require('./mystyles.less')) work in exactly the same way on the server as
// on the client.
import 'es6-promise';
import * as path from 'path';
import * as webpack from 'webpack';
import { requireNewCopy } from './RequireNewCopy';
// Strange import syntax to work around https://github.com/Microsoft/TypeScript/issues/2719
import { requirefromstring } from './typings/require-from-string';
import { memoryfs } from './typings/memory-fs';
const nodeExternals = require('webpack-node-externals');
const requireFromString = require('require-from-string') as typeof requirefromstring.requireFromString;
const MemoryFS = require('memory-fs') as typeof memoryfs.MemoryFS;
// Ensure we only go through the compile process once per [config, module] pair
const loadViaWebpackPromisesCache: { [key: string]: any } = {};
export interface LoadViaWebpackCallback<T> {
(error: any, result: T): void;
}
export function loadViaWebpack<T>(webpackConfigPath: string, modulePath: string, callback: LoadViaWebpackCallback<T>) {
const cacheKey = JSON.stringify(webpackConfigPath) + JSON.stringify(modulePath);
if (!(cacheKey in loadViaWebpackPromisesCache)) {
loadViaWebpackPromisesCache[cacheKey] = loadViaWebpackNoCache(webpackConfigPath, modulePath);
}
loadViaWebpackPromisesCache[cacheKey].then(result => {
callback(null, result);
}, error => {
callback(error, null);
})
}
function setExtension(filePath: string, newExtension: string) {
const oldExtensionIfAny = path.extname(filePath);
const basenameWithoutExtension = path.basename(filePath, oldExtensionIfAny);
return path.join(path.dirname(filePath), basenameWithoutExtension) + newExtension;
}
function loadViaWebpackNoCache<T>(webpackConfigPath: string, modulePath: string) {
return new Promise<T>((resolve, reject) => {
// Load the Webpack config and make alterations needed for loading the output into Node
const webpackConfig: webpack.Configuration = requireNewCopy(webpackConfigPath);
webpackConfig.entry = modulePath;
webpackConfig.target = 'node';
// Make sure we preserve the 'path' and 'publicPath' config values if specified, as these
// can affect the build output (e.g., when using 'file' loader, the publicPath value gets
// set as a prefix on output paths).
webpackConfig.output = webpackConfig.output || {};
webpackConfig.output.path = webpackConfig.output.path || '/';
webpackConfig.output.filename = 'webpack-output.js';
webpackConfig.output.libraryTarget = 'commonjs';
const outputVirtualPath = path.join(webpackConfig.output.path, webpackConfig.output.filename);
// In Node, we want any JavaScript modules under /node_modules/ to be loaded natively and not bundled into the
// output (partly because it's faster, but also because otherwise there'd be different instances of modules
// depending on how they were loaded, which could lead to errors).
// ---
// NOTE: We have to use webpack-node-externals rather than webpack-externals-plugin because
// webpack-externals-plugin doesn't correctly resolve relative paths, which means you can't
// use css-loader, since tries to require('./../../node_modules/css-loader/lib/css-base.js') (see #132)
// ---
// So, ensure that webpackConfig.externals is an array, and push WebpackNodeExternals into it:
let externalsArray: any[] = (webpackConfig.externals as any[]) || [];
if (!(externalsArray instanceof Array)) {
externalsArray = [externalsArray];
}
webpackConfig.externals = externalsArray;
externalsArray.push(nodeExternals({
// However, we do *not* want to treat non-JS files under /node_modules/ as externals (i.e., things
// that should be loaded via regular CommonJS 'require' statements). For example, if you reference
// a .css file inside an NPM module (e.g., require('somepackage/somefile.css')), then we do need to
// load that via Webpack rather than as a regular CommonJS module.
//
// So, configure webpack-externals-plugin to 'whitelist' (i.e., not treat as external) any file
// that has an extension other than .js. Also, since some libraries such as font-awesome refer to
// their own files with cache-busting querystrings (e.g., (url('./something.css?v=4.1.2'))), we
// need to treat '?' as an alternative 'end of filename' marker.
//
// The complex, awkward regex can be eliminated once webpack-externals-plugin merges
// https://github.com/liady/webpack-node-externals/pull/12
//
// This regex looks for at least one dot character that is *not* followed by "js<end-or-questionmark>", but
// is followed by some series of non-dot characters followed by <end-or-questionmark>:
whitelist: [/\.(?!js(\?|$))([^.]+(\?|$))/]
}));
// The CommonsChunkPlugin is not compatible with a CommonJS environment like Node, nor is it needed in that case
webpackConfig.plugins = webpackConfig.plugins.filter(plugin => {
return !(plugin instanceof webpack.optimize.CommonsChunkPlugin);
});
// The typical use case for DllReferencePlugin is for referencing vendor modules. In a Node
// environment, it doesn't make sense to load them from a DLL bundle, nor would that even
// work, because then you'd get different module instances depending on whether a module
// was referenced via a normal CommonJS 'require' or via Webpack. So just remove any
// DllReferencePlugin from the config.
// If someone wanted to load their own DLL modules (not an NPM module) via DllReferencePlugin,
// that scenario is not supported today. We would have to add some extra option to the
// asp-prerender tag helper to let you specify a list of DLL bundles that should be evaluated
// in this context. But even then you'd need special DLL builds for the Node environment so that
// external dependencies were fetched via CommonJS requires, so it's unclear how that could work.
// The ultimate escape hatch here is just prebuilding your code as part of the application build
// and *not* using asp-prerender-webpack-config at all, then you can do anything you want.
webpackConfig.plugins = webpackConfig.plugins.filter(plugin => {
// DllReferencePlugin is missing from webpack.d.ts for some reason, hence referencing it
// as a key-value object property
return !(plugin instanceof webpack['DllReferencePlugin']);
});
// Create a compiler instance that stores its output in memory, then load its output
const compiler = webpack(webpackConfig);
compiler.outputFileSystem = new MemoryFS();
compiler.run((err, stats) => {
if (err) {
reject(err);
} else {
// We're in a callback, so need an explicit try/catch to propagate any errors up the promise chain
try {
if (stats.hasErrors()) {
throw new Error('Webpack compilation reported errors. Compiler output follows: '
+ stats.toString({ chunks: false }));
}
// The dynamically-built module will only appear in node-inspector if it has some nonempty
// file path. The following value is arbitrary (since there's no real compiled file on disk)
// but is sufficient to enable debugging.
const fakeModulePath = setExtension(modulePath, '.js');
const fileContent = compiler.outputFileSystem.readFileSync(outputVirtualPath, 'utf8');
const moduleInstance = requireFromString<T>(fileContent, fakeModulePath);
resolve(moduleInstance);
} catch(ex) {
reject(ex);
}
}
});
});
}