├── .eslintignore
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── lib
├── config.js
├── extend-require.js
├── index.js
├── isomorphic-loader.js
├── logger.js
├── posixify.js
├── utils.js
└── webpack-plugin.js
├── package.json
├── test
├── .eslintrc
├── bad-version-config.json
├── client
│ ├── data
│ │ └── foo.bin
│ ├── entry.js
│ ├── fonts
│ │ └── font.ttf
│ ├── foo.js
│ └── images
│ │ ├── smiley.jpg
│ │ ├── smiley.png
│ │ ├── smiley.svg
│ │ └── smiley2.jpg
├── lib
│ ├── isomorphic.dev.spec.js
│ ├── isomorphic.spec.js
│ └── webpack-info.js
├── mocha.opts
├── nm
│ ├── demo.css
│ └── smiley2.jpg
├── spec
│ ├── extend-require.spec.js
│ ├── isomorphic.dev.spec.js
│ ├── isomorphic.spec.js
│ ├── loader.spec.js
│ ├── logger.spec.js
│ └── utils.spec.js
├── webpack.config.js
├── webpack3
│ ├── package.json
│ └── xrequire.js
└── webpack4.config.js
└── xclap.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | test/dist
2 | node_modules
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Node template
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
19 | .grunt
20 |
21 | # node-waf configuration
22 | .lock-wscript
23 |
24 | # Compiled binary addons (http://nodejs.org/api/addons.html)
25 | build/Release
26 |
27 | # Dependency directory
28 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
29 | node_modules
30 | ### JetBrains template
31 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
32 |
33 | *.iml
34 |
35 | ## Directory-based project format:
36 | .idea/
37 | # if you remove the above rule, at least ignore the following:
38 |
39 | # User-specific stuff:
40 | # .idea/workspace.xml
41 | # .idea/tasks.xml
42 | # .idea/dictionaries
43 |
44 | # Sensitive or high-churn files:
45 | # .idea/dataSources.ids
46 | # .idea/dataSources.xml
47 | # .idea/sqlDataSources.xml
48 | # .idea/dynamic.xml
49 | # .idea/uiDesigner.xml
50 |
51 | # Gradle:
52 | # .idea/gradle.xml
53 | # .idea/libraries
54 |
55 | # Mongo Explorer plugin:
56 | # .idea/mongoSettings.xml
57 |
58 | ## File-based project format:
59 | *.ipr
60 | *.iws
61 |
62 | ## Plugin-specific files:
63 |
64 | # IntelliJ
65 | /out/
66 |
67 | # mpeltonen/sbt-idea plugin
68 | .idea_modules/
69 |
70 | # JIRA plugin
71 | atlassian-ide-plugin.xml
72 |
73 | # Crashlytics plugin (for Android Studio and IntelliJ)
74 | com_crashlytics_export_strings.xml
75 | crashlytics.properties
76 | crashlytics-build.properties
77 | ### GitBook template
78 | # Node rules:
79 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
80 | .grunt
81 |
82 | ## Dependency directory
83 | ## Commenting this out is preferred by some people, see
84 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git
85 | node_modules
86 |
87 | # Book build output
88 | _book
89 |
90 | # eBook build output
91 | *.epub
92 | *.mobi
93 | *.pdf
94 |
95 | test/dist
96 | .isomorphic-loader*
97 | .nyc_output
98 | .vscode
99 | *-lock.*
100 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - v8
4 | - v10
5 | before_install: npm install -g fyn
6 | install: fyn --pg none install
7 | script: npm run coverage
8 | notifications:
9 | email:
10 | - joel123@gmail.com
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016-present, WalmartLabs
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url]
2 | [![Dependency Status][daviddm-image]][daviddm-url] [![devDependency Status][daviddm-dev-image]][daviddm-dev-url]
3 |
4 | # Webpack Isomorphic Loader
5 |
6 | Webpack loader and tools to make node.js `require` understands files such as images when you are doing server side rendering (SSR).
7 |
8 | ## Purpose
9 |
10 | With [webpack] and [file-loader], you can do things like this in your React code:
11 |
12 | ```js
13 | import smiley from "./images/smiley.jpg";
14 |
15 |
16 | render() {
17 | return

18 | }
19 | ```
20 |
21 | That works out nicely, but if you need to do SSR, you will get `SyntaxError` from node.js `require`. That's because `require` only understands JS files.
22 |
23 | With this module, you can extend `require` so it understands these files.
24 |
25 | It contains three parts:
26 |
27 | 1. a webpack loader - to mark asset files
28 | 2. a webpack plugin - collect asset files and generate mapping data
29 | 3. a node.js library - extend `require` for SSR using the mapping data
30 |
31 | ## Install
32 |
33 | ```
34 | $ npm install isomorphic-loader --save
35 | ```
36 |
37 | ## Usage
38 |
39 | ### Configuring Webpack
40 |
41 | First use the webpack loader `isomorphic-loader` to mark all your asset files that you want `extendRequire` to handle.
42 |
43 | > The webpack loader `isomorphic-loader` is just a simple pass thru loader to mark your files. It will not do anything to the file content.
44 |
45 | Next install the webpack plugin `IsomorphicLoaderPlugin` to collect and save the list of the marked files.
46 |
47 | For example, in the webpack config, to mark the usual image files to be understood by `extendRequire`:
48 |
49 | ```js
50 | const { IsomorphicLoaderPlugin } = require("isomorphic-loader");
51 |
52 | module.exports = {
53 | plugins: [new IsomorphicLoaderPlugin()],
54 | module: {
55 | loaders: [
56 | {
57 | test: /\.(jpe?g|png|gif|svg)$/i,
58 | loader: "file!isomorphic"
59 | }
60 | ]
61 | }
62 | };
63 | ```
64 |
65 | You can also mark any file in your code directly:
66 |
67 | ```js
68 | import smiley from "file!isomorphic!./images/smiley.jpg";
69 | ```
70 |
71 | ### Extending node.js `require`
72 |
73 | With the marked asset files collected, initialize `extendRequire` with the mapping data before your server starts:
74 |
75 | ```js
76 | const { extendRequire } = require("isomorphic-loader");
77 |
78 | const isomorphicRequire = extendRequire();
79 |
80 | // isomorphicRequire is an instance of the ExtendRequire class exported from the module
81 |
82 | // start your server etc
83 | ```
84 |
85 | It will try to load the isomorphic config data from `dist/isomorphic-assets.json`. You can also pass in the config data:
86 |
87 | ```js
88 | extendRequire(options, require("./dist/isomorphic-assets.json"));
89 | ```
90 |
91 | #### Custom Config Overrides
92 |
93 | When calling `extendRequire`, you can pass in a callback in `options.processConfig` to override the `isomorphicConfig`
94 |
95 | ```js
96 | extendRequire({
97 | processConfig: config => {
98 | // do something with config
99 | return config;
100 | }
101 | });
102 | ```
103 |
104 | #### Activating and Deactivating extendRequire
105 |
106 | - `deactivate` API - deactivate `extendRequire` during run time.
107 | - `activate` API - activate `extendRequire` during run time.
108 |
109 | ```js
110 | const { extendRequire } = require("isomorphic-loader");
111 |
112 | const isomorphicRequire = extendRequire();
113 |
114 | isomorphicRequire.deactivate();
115 |
116 | // and reactivate it
117 |
118 | isomorphicRequire.activate();
119 | ```
120 |
121 | ### Usage with CDN Server
122 |
123 | If you publish your assets to a Content Delivery Network server, and if it generates a new unique path for your assets, then you likely have to set `publicPath` after webpack compiled your project.
124 |
125 | That's why [webpack]'s document has this note in the [section about `publicPath`]:
126 |
127 | > **Note:** In cases when the eventual `publicPath` of output files isn't known at compile time, it can be left blank and set dynamically at runtime in the entry point file.
128 | > If you don't know the `publicPath` while compiling you can omit it and set `__webpack_public_path__` on your entry point.
129 |
130 | In that case, you would have to save the path CDN created for you and pass it to `extendRequire` with a [custom config override](#custom-config-overrides), or you can just modify the [config file](#config-and-assets-files) directly.
131 |
132 | If your CDN server generates an unique URL for every asset file instead of a single base path, then you have to do some custom post processing to update the asset mapping files yourself.
133 |
134 | ### Webpack Dev Server
135 |
136 | If you are using webpack dev server, then you probably have two separate processes running:
137 |
138 | 1. webpack dev server (WDS)
139 | 2. your app's node.js server (APP)
140 |
141 | And `IsomorphicLoaderPlugin` would be running in **WDS** but `extendRequire` is in **APP**.
142 |
143 | - You need to setup some way to transfer the mapping data from **WDS** to **APP**.
144 | - When starting up, **APP** needs to wait for the first mapping data before actually startup, unless your SSR code is not loaded until it's actually invoked.
145 |
146 | Here is an example using chokidar to transfer the data through a file:
147 |
148 | - In your `webpack.config.js`:
149 |
150 | ```js
151 | const fs = require("fs");
152 | const { IsomorphicLoaderPlugin } = require("isomorphic-loader");
153 |
154 | const isoPlugin = new IsomorphicLoaderPlugin();
155 | isoPlugin.on("update", data => {
156 | fs.writeFileSync("./tmp/isomorphic-assets.json", JSON.stringify(data.config));
157 | });
158 |
159 | module.exports = {
160 | plugins: [isoPlugin]
161 | };
162 | ```
163 |
164 | - In your app server `index.js`
165 |
166 | ```js
167 | const { extendRequire } = require("isomorphic-loader");
168 |
169 | // figure out if running in dev mode or not
170 | if (process.env.NODE_ENV !== "production") {
171 | const chokidar = require("chokidar");
172 | const assetFile = "./tmp/isomorphic-assets.json";
173 |
174 | let isomorphicRequire;
175 |
176 | function updateIsomorphicAssets() {
177 | const firstTime = !isomorphicRequire;
178 | if (firstTime) {
179 | isomorphicRequire = extendRequire();
180 | } else {
181 | // do the necessary require cache refresh so hot module reload works in SSR
182 | }
183 |
184 | isomorphicRequire.loadAssets(assetFile);
185 | if (firstTime) {
186 | startServer();
187 | }
188 | }
189 |
190 | const watcher = chokidar.watch(assetFile, { persistent: true });
191 | watcher.on("add", updateIsomorphicAssets);
192 | watcher.on("change", updateIsomorphicAssets);
193 |
194 | // do some timeout check
195 | setTimeout(() => {
196 | if (!isomorphicRequire) {
197 | console.error("timeout waiting for webpack dev server");
198 | }
199 | }, 20000).unref();
200 | } else {
201 | extendRequire().loadAssets("./dist/isomorphic-assets.json");
202 | startServer();
203 | }
204 | ```
205 |
206 | ## License
207 |
208 | [Apache License, Version 2]
209 |
210 | [file-loader]: https://github.com/webpack/file-loader
211 | [apache license, version 2]: https://www.apache.org/licenses/LICENSE-2.0
212 | [webpack-dev-server]: https://webpack.github.io/docs/webpack-dev-server.html
213 | [webpack]: https://webpack.github.io/
214 | [section about `publicpath`]: https://github.com/webpack/docs/wiki/configuration#outputpublicpath
215 | [travis-image]: https://travis-ci.org/electrode-io/isomorphic-loader.svg?branch=master
216 | [travis-url]: https://travis-ci.org/electrode-io/isomorphic-loader
217 | [npm-image]: https://badge.fury.io/js/isomorphic-loader.svg
218 | [npm-url]: https://npmjs.org/package/isomorphic-loader
219 | [daviddm-image]: https://david-dm.org/electrode-io/isomorphic-loader/status.svg
220 | [daviddm-url]: https://david-dm.org/electrode-io/isomorphic-loader
221 | [daviddm-dev-image]: https://david-dm.org/electrode-io/isomorphic-loader/dev-status.svg
222 | [daviddm-dev-url]: https://david-dm.org/electrode-io/isomorphic-loader?type=dev
223 |
--------------------------------------------------------------------------------
/lib/config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const configName = `isomorphic-loader-config`;
4 |
5 | module.exports = {
6 | configName,
7 | defaultAssetsFile: "dist/isomorphic-assets.json"
8 | };
9 |
--------------------------------------------------------------------------------
/lib/extend-require.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* eslint-disable max-statements, max-len, complexity, prefer-template, no-magic-numbers */
4 |
5 | const Path = require("path");
6 | const Fs = require("fs");
7 | const Module = require("module");
8 | const Config = require("./config");
9 | const { removeLoaders, removeCwd, getParentPath, replaceAppSrcDir } = require("./utils");
10 | const Pkg = require("../package.json");
11 | const logger = require("./logger");
12 | const assert = require("assert");
13 | const posixify = require("./posixify");
14 | const EventEmitter = require("events");
15 |
16 | const LOG_PREFIX = "isomorphic-loader";
17 |
18 | class ExtendRequire extends EventEmitter {
19 | constructor(options) {
20 | super();
21 | this.assetsCount = 0;
22 | this.clearData();
23 | this.options = { cwd: process.cwd(), ...options };
24 | this._urlMap = this.urlMap;
25 | this._originalLoad = undefined;
26 | this.activated = false;
27 | this.interceptLoad();
28 | }
29 |
30 | /**
31 | * Clear config and assets to contain nothing
32 | *
33 | * @returns {void} nothing
34 | */
35 | clearData() {
36 | this.assets = { marked: {} };
37 | this.config = {};
38 | this._markedNames = [];
39 | }
40 |
41 | /**
42 | * Determine if isomorphic data is generated by webpack running in dev mode
43 | *
44 | * @returns {boolean} true if webpack is running in dev mode
45 | */
46 | isWebpackDev() {
47 | return Boolean(this.config.isWebpackDev);
48 | }
49 |
50 | /**
51 | * Stop extend node.js require, restore original require, and clear config and assets data.
52 | *
53 | * @returns {*} undefined
54 | */
55 | reset() {
56 | this.deactivate();
57 | this.clearData();
58 |
59 | if (this._originalLoad) {
60 | Module._load = this._originalLoad;
61 | this._originalLoad = undefined;
62 | }
63 | this.emit("reset");
64 | }
65 |
66 | /**
67 | * set base publicPath for creating the URL of the assets
68 | *
69 | * @param {*} p - public path (undefined to unset it)
70 | * @returns {void} none
71 | */
72 | setPublicPath(p) {
73 | this._publicPath = (p !== undefined ? p : this.config.output.publicPath) || "";
74 | }
75 |
76 | /**
77 | * Set a callback function that take an asset URL and map it to something else
78 | * @param {function} mapper - callback
79 | * @returns {void} none
80 | */
81 | setUrlMapper(mapper) {
82 | this._urlMap = mapper;
83 | }
84 |
85 | /**
86 | * built-in URL mapper, which just prepend publicPath to it
87 | *
88 | * @param {string} url - url to map
89 | * @returns {string} updated URL
90 | */
91 | urlMap(url) {
92 | return this._publicPath + url;
93 | }
94 |
95 | /**
96 | * Set the assets mapping to use for modified require behavior
97 | *
98 | * @param {*} config - isomorphic config generated from the webpack plugin
99 | * @returns {*} undefined
100 | */
101 | initialize(config) {
102 | this.config = { output: {}, ...config };
103 |
104 | if (this.options.processConfig) {
105 | this.config = this.options.processConfig(this.config);
106 | }
107 |
108 | if (this._publicPath === undefined) {
109 | this.setPublicPath();
110 | }
111 |
112 | this.assets = { marked: {}, ...this.config.assets };
113 | this._markedNames = Object.keys(this.assets.marked);
114 | assert(
115 | this.config.version === Pkg.version,
116 | `${LOG_PREFIX}: this module version ${Pkg.version} is different from config version ${this.config.version}`
117 | );
118 | this.activate();
119 | this.interceptLoad();
120 | }
121 |
122 | /**
123 | * stop mapping non-standard JS module for node.js require
124 | *
125 | * @returns {*} undefined
126 | */
127 | deactivate() {
128 | this.assetsCount = 0;
129 | this.activated = false;
130 | this.emit("deactivate");
131 | }
132 |
133 | /**
134 | * Look for and load assets from `dist/isomorphic-assets.json` or the array of files passed in
135 | * @param {*} customFilePath - optional file to look for and load
136 | * @returns {string} the name of the file that's loaded
137 | */
138 | loadAssets(customFilePath) {
139 | return []
140 | .concat(customFilePath, this.options.assetsFile, Config.defaultAssetsFile)
141 | .filter(x => x)
142 | .find(file => {
143 | try {
144 | const config = JSON.parse(Fs.readFileSync(file, "utf-8"));
145 | this.initialize(config);
146 | return true;
147 | } catch (err) {
148 | if (err.code !== "ENOENT") {
149 | throw new Error(`${LOG_PREFIX}: fail to load config from ${file} - ${err.message}`);
150 | }
151 | return false;
152 | }
153 | });
154 | }
155 |
156 | /**
157 | * start mapping non-standard JS module for node.js require.
158 | *
159 | * - assets must have been set with `setAssets`
160 | * @returns {*} undefined
161 | */
162 | activate() {
163 | this.assetsCount = this._markedNames.length;
164 | this.activated = true;
165 | this.emit("activate");
166 | }
167 |
168 | interceptLoad() {
169 | if (this._originalLoad) {
170 | return;
171 | }
172 |
173 | const appSrcDir = this.options.appSrcDir;
174 |
175 | const isAssetNotFound = request => {
176 | const x = Path.basename(request);
177 | return this._markedNames.find(name => Path.basename(name).indexOf(x) >= 0);
178 | };
179 |
180 | const checkAsset = (moduleInstance, request, parent) => {
181 | const config = this.config;
182 | if (config.output && this.assetsCount > 0) {
183 | let requestMarkKey;
184 | const xRequest = removeLoaders(request);
185 |
186 | if (xRequest.startsWith(".")) {
187 | const parentPath = posixify(removeCwd(getParentPath(parent), true, this.options.cwd));
188 | requestMarkKey = Path.posix.join(parentPath, xRequest);
189 | } else {
190 | try {
191 | requestMarkKey = posixify(
192 | removeCwd(moduleInstance._resolveFilename(xRequest, parent), false, this.options.cwd)
193 | );
194 | } catch (e) {
195 | if (isAssetNotFound(xRequest)) {
196 | logger.error(LOG_PREFIX, "check asset " + xRequest + " exception", e);
197 | }
198 | return undefined;
199 | }
200 | }
201 |
202 | let assetUrl = this.assets.marked[replaceAppSrcDir(requestMarkKey, appSrcDir)];
203 |
204 | if (assetUrl) {
205 | if (/\.(css|less|styl|sass|scss)$/.test(requestMarkKey)) {
206 | return assetUrl;
207 | }
208 | try {
209 | assetUrl = this._urlMap(assetUrl, request, appSrcDir);
210 | } catch (err) {
211 | logger.error(LOG_PREFIX, "urlMap thrown error", err);
212 | }
213 |
214 | if (config.isWebpackDev && config.webpackDev.addUrl && config.webpackDev.url) {
215 | const sep =
216 | !config.webpackDev.url.endsWith("/") && !assetUrl.startsWith("/") ? "/" : "";
217 | assetUrl = config.webpackDev.url + sep + assetUrl;
218 | }
219 |
220 | return assetUrl;
221 | }
222 | }
223 |
224 | if (this.options.ignoreExtensions) {
225 | const ext = Path.extname(request);
226 | if (ext && this.options.ignoreExtensions.includes(ext)) {
227 | return {};
228 | }
229 | }
230 |
231 | return undefined;
232 | };
233 |
234 | const originalLoad = (this._originalLoad = Module._load);
235 |
236 | Module._load = function(request, parent, isMain) {
237 | const asset = checkAsset(this, request, parent);
238 | if (asset !== undefined) {
239 | return asset;
240 | }
241 |
242 | return originalLoad.apply(this, [request, parent, isMain]);
243 | };
244 | }
245 | }
246 |
247 | module.exports = { ExtendRequire };
248 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const { ExtendRequire } = require("./extend-require");
4 | const { IsomorphicLoaderPlugin } = require("./webpack-plugin");
5 | const IsomorphicLoader = require("./isomorphic-loader");
6 |
7 | /**
8 | * Initialize node.js extend require to handle isomorphic assets in SSR
9 | * @param {*} options - options
10 | * @param {*} isomorphicConfig - isomorphic config data
11 | *
12 | * @returns {*} an instance of ExtendRequire
13 | */
14 | function extendRequire(options, isomorphicConfig) {
15 | const extReq = new ExtendRequire(options);
16 |
17 | if (isomorphicConfig) {
18 | extReq.initialize(isomorphicConfig);
19 | } else if (!process.env.hasOwnProperty("WEBPACK_DEV")) {
20 | //
21 | // don't try to load assets in webpack dev mode, because there could be an outdated
22 | // and staled dist directory with a config file that's bad. User may have
23 | // updated dependencies and run dev but didn't do a build to update dist yet, and
24 | // that could cause loading the assets to have unexpected failures
25 | //
26 | extReq.loadAssets();
27 | }
28 |
29 | return extReq;
30 | }
31 |
32 | IsomorphicLoader.extendRequire = extendRequire;
33 | IsomorphicLoader.ExtendRequire = ExtendRequire;
34 | IsomorphicLoader.IsomorphicLoaderPlugin = IsomorphicLoaderPlugin;
35 |
36 | const GLOBAL_INSTANCE = Symbol.for("isomorphic-loader-global-extend-require");
37 |
38 | IsomorphicLoader.setXRequire = instance => (global[GLOBAL_INSTANCE] = instance);
39 | IsomorphicLoader.getXRequire = () => global[GLOBAL_INSTANCE];
40 |
41 | module.exports = IsomorphicLoader;
42 |
--------------------------------------------------------------------------------
/lib/isomorphic-loader.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const { getCssModuleGlobalMapping } = require("./utils");
4 |
5 | module.exports = function (content) {
6 | const userRequest = (this._module && this._module.userRequest) || "";
7 | if (/\.(css|less|styl|sass|scss)$/.test(userRequest)) {
8 | const mapping = getCssModuleGlobalMapping();
9 | mapping[userRequest] = content.toString("utf-8");
10 | }
11 | this.cacheable && this.cacheable(); // eslint-disable-line
12 | return content;
13 | };
14 |
15 | module.exports.raw = true;
16 |
--------------------------------------------------------------------------------
/lib/logger.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const LEVELS = {
4 | info: 10,
5 | error: 99,
6 | none: 999
7 | };
8 |
9 | let LEVEL = LEVELS.info;
10 |
11 | function log() {
12 | if (LEVEL <= LEVELS.info) {
13 | console.log.apply(console, arguments); // eslint-disable-line
14 | }
15 | }
16 |
17 | function error() {
18 | if (LEVEL <= LEVELS.error) {
19 | console.error.apply(console, arguments); // eslint-disable-line
20 | }
21 | }
22 |
23 | module.exports = { log: log, error: error, setLevel: l => (LEVEL = LEVELS[l] || LEVELS.info) };
24 |
--------------------------------------------------------------------------------
/lib/posixify.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* istanbul ignore next */
4 |
5 | module.exports = process.platform.startsWith("win32") ? p => p.replace(/\\/g, "/") : p => p;
6 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const Path = require("path");
4 | const posixify = require("./posixify");
5 |
6 | const CSS_MODULE_MAPPINGS = Symbol.for("css-module-content-mapping");
7 |
8 | /**
9 | * Get the path of a parent module that's making a require call
10 | *
11 | * @param {*} parent module
12 | * @returns {string} parent module's path
13 | */
14 | exports.getParentPath = parent => {
15 | return (parent && (parent.path || (parent.filename && Path.dirname(parent.filename)))) || "";
16 | };
17 |
18 | /**
19 | * Get the verbatim string as passed to require that webpack processed
20 | * @param {*} mod module
21 | * @returns {string} require request
22 | *
23 | */
24 | exports.getWebpackRequest = mod => {
25 | const reason = mod && mod.reasons && mod.reasons[0];
26 | return (reason && reason.userRequest) || "";
27 | };
28 |
29 | /**
30 | * Remove CWD from path
31 | * @param {*} path - path
32 | * @param {*} last - if true then use lastIndexOf to search
33 | * @param {string} cwd - optional current working dir
34 | * @returns {string} new path
35 | */
36 | exports.removeCwd = (path, last, cwd = process.cwd()) => {
37 | const x = last ? path.lastIndexOf(cwd) : path.indexOf(cwd);
38 |
39 | if (x >= 0) {
40 | return path.substr(x + cwd.length + 1);
41 | }
42 |
43 | return path;
44 | };
45 |
46 | /**
47 | * remove webpack loader marks like "file-loader!" from a require request
48 | *
49 | * @param {string} request - require request
50 | * @returns {string} request without webpack loader marks
51 | */
52 | exports.removeLoaders = request => {
53 | const markIx = request.lastIndexOf("!");
54 | if (markIx >= 0) {
55 | return request.substr(markIx + 1);
56 | }
57 | return request;
58 | };
59 |
60 | /**
61 | * Replace app src dir in request path with a consistent marker
62 | *
63 | * @param {string} request - require request
64 | * @param {string} appSrcDir - app src dir
65 | * @returns {string} updated request
66 | */
67 | exports.replaceAppSrcDir = (request, appSrcDir) => {
68 | if (appSrcDir) {
69 | const asd = Path.posix.join(appSrcDir, "/");
70 | return request.replace(new RegExp(`^${asd}`), "$AppSrc/");
71 | }
72 | return request;
73 | };
74 |
75 | /**
76 | * Returns local to global classnames mapping for css modules
77 | *
78 | * @returns {object} mapping object
79 | */
80 | exports.getCssModuleGlobalMapping = () => {
81 | if (!global[CSS_MODULE_MAPPINGS]) {
82 | global[CSS_MODULE_MAPPINGS] = {};
83 | }
84 |
85 | return global[CSS_MODULE_MAPPINGS];
86 | };
87 |
88 | /**
89 | * Load node.js module from string
90 | *
91 | * @param {string} src - file's content
92 | * @param {string} filename - optional filename
93 | * @returns {object} exported object
94 | */
95 | exports.requireFromString = (src, filename = "") => {
96 | const Module = module.constructor;
97 | const m = new Module();
98 | m._compile(src, filename);
99 | return m.exports;
100 | };
101 |
102 | exports.getMyNodeModulesPath = (dir = __dirname) => {
103 | const myName = "isomorphic-loader";
104 | const ixName = dir.lastIndexOf(myName);
105 |
106 | if (ixName >= 0) {
107 | return posixify(dir.substr(ixName));
108 | }
109 |
110 | const parts = dir.split(Path.sep);
111 | // gather up to last 4 parts and join with posix path sep
112 | return parts.slice(Math.max(0, parts.length - 4)).join("/"); // eslint-disable-line
113 | };
114 |
--------------------------------------------------------------------------------
/lib/webpack-plugin.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* eslint-disable no-unused-expressions, prefer-template, no-magic-numbers, max-statements */
4 |
5 | // https://webpack.github.io/docs/how-to-write-a-plugin.html
6 |
7 | const Path = require("path");
8 | const Config = require("./config");
9 | const {
10 | getWebpackRequest,
11 | removeLoaders,
12 | removeCwd,
13 | replaceAppSrcDir,
14 | getCssModuleGlobalMapping,
15 | getMyNodeModulesPath,
16 | requireFromString
17 | } = require("./utils");
18 | const Pkg = require("../package.json");
19 | const posixify = require("./posixify");
20 | const EventEmitter = require("events");
21 |
22 | const pluginName = "IsomorphicLoaderPlugin";
23 |
24 | class IsomorphicLoaderPlugin extends EventEmitter {
25 | /**
26 | *
27 | * @param {*} options options
28 | *
29 | */
30 | constructor(options) {
31 | super();
32 | this.config = { valid: false };
33 | this.options = { cwd: process.cwd(), assetsFile: "isomorphic-assets.json", ...options };
34 |
35 | const directJs = Path.posix.join(getMyNodeModulesPath(), "index.js!");
36 | this._loaderSigs = [directJs, `isomorphic-loader/lib/index.js!`, `isomorphic-loader!`];
37 | }
38 |
39 | apply(compiler) {
40 | const createConfig = (compilerOpts, assets = { marked: {}, chunks: {} }) => {
41 | //
42 | // There is no easy way to detect that webpack-dev-server is running, but it
43 | // does change the output path to "/". Since that normally is "build" or "dist"
44 | // and it's unlikely anyone would use "/", assuming it's webpack-dev-server if
45 | // output path is "/".
46 | //
47 | const isWebpackDev =
48 | compilerOpts.output.path === "/" ||
49 | require.main.filename.indexOf("webpack-dev-server") >= 0;
50 |
51 | const config = {
52 | valid: false,
53 | version: Pkg.version,
54 | timestamp: Date.now(),
55 | context: removeCwd(compilerOpts.context, false, this.options.cwd),
56 | output: {
57 | path: removeCwd(compilerOpts.output.path, false, this.options.cwd),
58 | filename: removeCwd(compilerOpts.output.filename, false, this.options.cwd),
59 | publicPath: compilerOpts.output.publicPath || ""
60 | },
61 | assets
62 | };
63 |
64 | if (isWebpackDev) {
65 | config.webpackDev = Object.assign(
66 | {
67 | skipSetEnv: false,
68 | url: "http://localhost:8080",
69 |
70 | // should extend require prepend webpack dev URL when returning URL for asset file
71 | addUrl: true
72 | },
73 | this.options.webpackDev
74 | );
75 | config.isWebpackDev = true;
76 | }
77 |
78 | return config;
79 | };
80 |
81 | const updateConfigForDevMode = () => {
82 | if (!this.config.isWebpackDev) {
83 | return;
84 | }
85 |
86 | this.emit("update", { name: Config.configName, config: this.config });
87 | };
88 |
89 | const handleEmit = (compilation, webpackAssets) => {
90 | const stats = compilation.getStats().toJson();
91 |
92 | const marked = stats.modules.reduce((acc, m) => {
93 | const ident = posixify(m.identifier);
94 | const foundSig = this._loaderSigs.find(sig => ident.includes(sig));
95 | if (foundSig && m.assets && m.assets.length > 0) {
96 | const userRequest = removeLoaders(getWebpackRequest(m));
97 |
98 | let requestMarkKey;
99 | // if userRequest starts with ., it's a relative path, so try to reconstruct its
100 | // full path by joining it with the issuer path
101 | // npm module name can't start with ., so it's not needed to check ./ or ../
102 | if (userRequest.startsWith(".")) {
103 | const issuerPath = posixify(
104 | Path.dirname(removeCwd(removeLoaders(m.issuer), true, this.options.cwd))
105 | );
106 | // require paths always use / so use posix path
107 | requestMarkKey = Path.posix.join(issuerPath, userRequest);
108 | } else {
109 | // no need to worry about target's location relative to issuer's location, just
110 | // use full identifier path directly
111 | requestMarkKey = posixify(
112 | removeCwd(removeLoaders(m.identifier), false, this.options.cwd)
113 | );
114 | }
115 |
116 | acc[replaceAppSrcDir(requestMarkKey, this.options.appSrcDir)] =
117 | m.assets[m.assets.length - 1];
118 | }
119 |
120 | return acc;
121 | }, {});
122 |
123 | const cssModuleMap = getCssModuleGlobalMapping();
124 | Object.keys(cssModuleMap).forEach(userRequest => {
125 | const requestKey = posixify(removeCwd(userRequest, false, this.options.cwd));
126 | const content = requireFromString(cssModuleMap[userRequest]);
127 | marked[replaceAppSrcDir(requestKey, this.options.appSrcDir)] = content;
128 | });
129 |
130 | const assets = {
131 | marked,
132 | chunks: stats.assetsByChunkName
133 | };
134 |
135 | const config = createConfig(compiler.options, assets);
136 | config.valid = true;
137 |
138 | const configStr = JSON.stringify(config, null, 2) + "\n";
139 |
140 | (webpackAssets || compilation.assets)[this.options.assetsFile] = {
141 | source: () => configStr,
142 | size: () => configStr.length
143 | };
144 |
145 | this.config = config;
146 | };
147 |
148 | const handleMake = (compilation, callback) => {
149 | this.config = createConfig(compiler.options);
150 |
151 | // If running in webpack dev server mode, then create a config file ASAP so not to leave
152 | // extend require waiting.
153 | updateConfigForDevMode();
154 |
155 | if (compiler.hooks && compilation.hooks.processAssets) {
156 | // handle emit for webpack 5
157 | compilation.hooks.processAssets.tap(pluginName, assets => handleEmit(compilation, assets));
158 | }
159 |
160 | /* istanbul ignore next */
161 | callback && callback();
162 | };
163 |
164 | const handleInvalid = () => {
165 | this.config.valid = false;
166 | updateConfigForDevMode();
167 | };
168 |
169 | /* istanbul ignore else */
170 | if (compiler.hooks) {
171 | // use .hooks
172 | compiler.hooks.make.tap(pluginName, handleMake);
173 | compiler.hooks.emit.tap(pluginName, compilation => {
174 | // handle emit for webpack 4
175 | if (!compilation.hooks.processAssets) {
176 | handleEmit(compilation, compilation.assets);
177 | }
178 | });
179 | compiler.hooks.invalid.tap(pluginName, handleInvalid);
180 | compiler.hooks.done.tap(pluginName, updateConfigForDevMode);
181 | } else {
182 | compiler.plugin("make", handleMake);
183 | compiler.plugin("emit", handleEmit);
184 | compiler.plugin("invalid", handleInvalid);
185 | compiler.plugin("done", updateConfigForDevMode);
186 | }
187 | }
188 | }
189 |
190 | module.exports = { IsomorphicLoaderPlugin };
191 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "isomorphic-loader",
3 | "version": "4.4.1",
4 | "description": "Webpack isomorphic loader tools to make Node require handle files like images for Server Side Rendering (SSR)",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "wp": "webpack --config test/webpack.config.js",
8 | "wpd": "webpack-dev-server --config test/webpack.config.js",
9 | "test": "clap test",
10 | "coverage": "clap check",
11 | "lint": "clap lint",
12 | "prepublishOnly": "clap check"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/electrode-io/isomorphic-loader.git"
17 | },
18 | "files": [
19 | "lib"
20 | ],
21 | "keywords": [
22 | "webpack",
23 | "isomorphic",
24 | "loader",
25 | "images",
26 | "assets",
27 | "SSR",
28 | "react",
29 | "server",
30 | "side",
31 | "rendering",
32 | "node",
33 | "nodejs",
34 | "require"
35 | ],
36 | "author": "Joel Chen",
37 | "license": "Apache-2.0",
38 | "dependencies": {},
39 | "devDependencies": {
40 | "clone": "^1.0.2",
41 | "css-loader": "^1.0.1",
42 | "electrode-archetype-njs-module-dev": "^3.0.0",
43 | "electrode-cdn-file-loader": "^1.1.1",
44 | "fetch": "^1.0.1",
45 | "file-loader": "^1.0.0",
46 | "lodash": "^4.17.20",
47 | "mini-css-extract-plugin": "^0.9.0",
48 | "prettier": "^1.19.1",
49 | "require-at": "^1.0.4",
50 | "rimraf": "^2.5.2",
51 | "run-verify": "^1.2.4",
52 | "webpack": "^4.1.0",
53 | "webpack-cli": "^2",
54 | "webpack-dev-server": "^3",
55 | "webpack3": "./test/webpack3",
56 | "xaa": "^1.2.2",
57 | "xstdout": "^0.1.1"
58 | },
59 | "nyc": {
60 | "all": true,
61 | "reporter": [
62 | "lcov",
63 | "text",
64 | "text-summary"
65 | ],
66 | "exclude": [
67 | "coverage",
68 | "*clap.js",
69 | "gulpfile.js",
70 | "dist",
71 | "test"
72 | ],
73 | "check-coverage": true,
74 | "statements": 100,
75 | "branches": 100,
76 | "functions": 100,
77 | "lines": 100,
78 | "cache": true
79 | },
80 | "engines": {
81 | "node": ">=10"
82 | },
83 | "prettier": {
84 | "printWidth": 100
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | ---
2 | env:
3 | "mocha": true
4 | rules:
5 | no-console: 0
6 |
--------------------------------------------------------------------------------
/test/bad-version-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "valid": true,
3 | "version": "3.0.1",
4 | "timestamp": 1602539652450,
5 | "context": "src",
6 | "output": {
7 | "path": "dist/js",
8 | "filename": "[name].bundle.[contenthash].js",
9 | "publicPath": "/js/"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/client/data/foo.bin:
--------------------------------------------------------------------------------
1 | test test
2 |
--------------------------------------------------------------------------------
/test/client/entry.js:
--------------------------------------------------------------------------------
1 | require("./images/smiley.jpg");
2 | require("./images/smiley2.jpg");
3 | require("./foo");
4 |
--------------------------------------------------------------------------------
/test/client/fonts/font.ttf:
--------------------------------------------------------------------------------
1 | testtesttest
--------------------------------------------------------------------------------
/test/client/foo.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | require("./images/smiley.svg");
3 | require("../..!./images/smiley.png");
4 | require("file-loader!../..!./data/foo.bin");
5 | require("./fonts/font.ttf");
6 | require("smiley2Jpg");
7 | require("demoCss");
8 |
9 | console.log("foo");
10 |
--------------------------------------------------------------------------------
/test/client/images/smiley.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchip/isomorphic-loader/f4e5fa18b778a517ebf7391e07092c9262d1c00c/test/client/images/smiley.jpg
--------------------------------------------------------------------------------
/test/client/images/smiley.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchip/isomorphic-loader/f4e5fa18b778a517ebf7391e07092c9262d1c00c/test/client/images/smiley.png
--------------------------------------------------------------------------------
/test/client/images/smiley.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/test/client/images/smiley2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchip/isomorphic-loader/f4e5fa18b778a517ebf7391e07092c9262d1c00c/test/client/images/smiley2.jpg
--------------------------------------------------------------------------------
/test/lib/isomorphic.dev.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* eslint-disable max-nested-callbacks */
4 |
5 | const fs = require("fs");
6 | const Path = require("path");
7 | const rimraf = require("rimraf");
8 | const chai = require("chai");
9 | const clone = require("clone");
10 | const _ = require("lodash");
11 | const fetchUrl = require("fetch").fetchUrl;
12 | const xaa = require("xaa");
13 | const { asyncVerify, runFinally } = require("run-verify");
14 |
15 | const expect = chai.expect;
16 |
17 | const { extendRequire, IsomorphicLoaderPlugin } = require("../..");
18 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
19 |
20 | module.exports = function isomorphicDevSpec({
21 | title,
22 | tag,
23 | timeout,
24 | webpack,
25 | WebpackDevServer,
26 | webpackConfig
27 | }) {
28 | describe(title, function() {
29 | this.timeout(timeout);
30 |
31 | webpackConfig.output.path = "/";
32 |
33 | const defaultFontHash = "1e2bf10d5113abdb2ca03d0d0f4f7dd1.ttf";
34 | const changedFontHash = "1fb0e331c05a52d5eb847d6fc018320d.ttf";
35 |
36 | function writeFont(data) {
37 | // default font file md5 1e2bf10d5113abdb2ca03d0d0f4f7dd1
38 | fs.writeFileSync(
39 | Path.resolve("test/client/fonts/font.ttf"),
40 | data || "ttfttfttf\nfontfontfont"
41 | );
42 | }
43 |
44 | function writeCss(data) {
45 | fs.writeFileSync(
46 | Path.resolve("test/nm/demo.css"),
47 | data || ".demo1 { background-color: greenyellow; }"
48 | );
49 | }
50 |
51 | function cleanup() {
52 | delete process.send;
53 | try {
54 | rimraf.sync(Path.resolve("test/dist"));
55 | writeFont();
56 | writeCss();
57 | } catch (e) {
58 | //
59 | }
60 | }
61 |
62 | let webpackDevServer;
63 |
64 | function start(config, devConfig, callback) {
65 | const compiler = webpack(config);
66 | webpackDevServer = new WebpackDevServer(compiler, _.merge({}, devConfig));
67 | webpackDevServer.listen(8080, "localhost", function() {
68 | callback();
69 | });
70 | }
71 |
72 | const devConfig = {
73 | host: "localhost",
74 | port: 8080,
75 | publicPath: webpackConfig.output.publicPath,
76 | // outputPath: "/",
77 | filename: webpackConfig.output.filename,
78 | hot: false,
79 | contentBase: process.cwd(),
80 | quiet: true,
81 | stats: {
82 | cached: false,
83 | cachedAssets: false,
84 | colors: {
85 | level: 2,
86 | hasBasic: true,
87 | has256: true,
88 | has16m: false
89 | }
90 | }
91 | };
92 |
93 | function stopWebpackDevServer(callback) {
94 | if (webpackDevServer) {
95 | webpackDevServer.close();
96 | webpackDevServer.listeningApp.close(function() {
97 | webpackDevServer = undefined;
98 | setTimeout(callback, 200);
99 | });
100 | } else {
101 | callback();
102 | }
103 | }
104 |
105 | before(cleanup);
106 |
107 | beforeEach(function(done) {
108 | cleanup();
109 | done();
110 | });
111 |
112 | let isomorphicRequire;
113 | let isomorphicConfig;
114 |
115 | afterEach(function(done) {
116 | // cleanup();
117 | isomorphicRequire && isomorphicRequire.reset();
118 | isomorphicRequire = undefined;
119 | isomorphicConfig = undefined;
120 |
121 | stopWebpackDevServer(done);
122 | });
123 |
124 | const fontFile = "test/client/fonts/font.ttf";
125 | const cssFile = "test/nm/demo.css";
126 |
127 | function verifyRemoteAssets(fontHash, callback) {
128 | fetchUrl("http://localhost:8080/test/isomorphic-assets.json", function(err, meta, body) {
129 | if (err) return callback(err);
130 | expect(meta.status).to.equal(200);
131 | const isomorphicData = JSON.parse(body.toString());
132 | expect(isomorphicData.assets.marked[fontFile]).to.equal(fontHash);
133 | expect(isomorphicData.assets.marked[cssFile]).to.deep.equal({ "demo1": "demo__demo1" });
134 | return callback();
135 | });
136 | }
137 |
138 | function testWebpackDevServer(config, plugin) {
139 | isomorphicRequire = extendRequire({});
140 |
141 | plugin.on("update", data => {
142 | isomorphicConfig = data.config;
143 | isomorphicRequire.initialize(data.config);
144 | });
145 |
146 | function verifyAssetChanges(callback) {
147 | const oldHash = isomorphicConfig.assets.marked[fontFile];
148 | expect(oldHash).to.be.a("string").that.is.not.empty;
149 | writeFont("testtesttest"); // font.ttf md5 1fb0e331c05a52d5eb847d6fc018320d
150 | writeCss(".demo { background-color: greenyellow; }");
151 |
152 | const startTime = Date.now();
153 | function check() {
154 | const newHash = isomorphicConfig.assets.marked[fontFile];
155 | const newMap = isomorphicConfig.assets.marked[cssFile];
156 | if (newHash && newHash !== oldHash) {
157 | expect(newHash).to.be.a("string").that.is.not.empty;
158 | expect(newHash).contains("1fb0e331c05a52d5eb847d6fc018320d");
159 | expect(newMap).to.deep.equal({ "demo": "demo__demo" });
160 | callback();
161 | } else if (Date.now() - startTime > 5000) {
162 | callback(new Error("waiting for font change valid message timeout"));
163 | } else {
164 | setTimeout(check, 50);
165 | }
166 | }
167 |
168 | check();
169 | }
170 |
171 | return asyncVerify(
172 | next => start(config, devConfig, next),
173 | () => expect(isomorphicRequire.isWebpackDev()).to.equal(true),
174 | next => verifyRemoteAssets(defaultFontHash, next),
175 | () => xaa.delay(25),
176 | next => verifyAssetChanges(next),
177 | runFinally(() => {
178 | plugin.removeAllListeners();
179 | })
180 | );
181 | }
182 |
183 | function testAddUrl(publicPath) {
184 | const wpConfig = clone(webpackConfig);
185 | wpConfig.output.publicPath = publicPath;
186 |
187 | const plugin = new IsomorphicLoaderPlugin({
188 | webpackDev: { url: "http://localhost:8080", addUrl: true }
189 | });
190 |
191 | wpConfig.plugins = [plugin, new MiniCssExtractPlugin({ filename: "[name].style.css" })];
192 |
193 | return asyncVerify(
194 | () => testWebpackDevServer(wpConfig, plugin),
195 | () => {
196 | const ttf = "../client/fonts/font.ttf";
197 | const fontFullPath = require.resolve(ttf);
198 | delete require.cache[fontFullPath];
199 | const font = require(ttf);
200 | expect(font).to.equal(`http://localhost:8080/test/${changedFontHash}`);
201 | delete require.cache[fontFullPath];
202 | },
203 | runFinally(() => {
204 | isomorphicRequire && isomorphicRequire.reset();
205 |
206 | return new Promise(resolve => stopWebpackDevServer(resolve));
207 | })
208 | );
209 | }
210 |
211 | it(`should start and add webpack dev server URL @${tag}`, function() {
212 | return testAddUrl("/test/", true);
213 | });
214 |
215 | it(`should start and add webpack dev server URL and / @${tag}`, function() {
216 | return testAddUrl("test/");
217 | });
218 | });
219 | };
220 |
--------------------------------------------------------------------------------
/test/lib/isomorphic.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* eslint-disable */
4 |
5 | const Fs = require("fs");
6 | const Path = require("path");
7 | const clone = require("clone");
8 | const chai = require("chai");
9 | const rimraf = require("rimraf");
10 | const Config = require("../../lib/config");
11 | const _ = require("lodash");
12 | const expect = chai.expect;
13 | const { extendRequire, setXRequire, getXRequire } = require("../..");
14 | const { asyncVerify, expectError, runFinally } = require("run-verify");
15 | const Pkg = require("../../package.json");
16 |
17 | const logger = require("../../lib/logger");
18 |
19 | module.exports = function isomorphicExtend({ tag, webpack, webpackConfig }) {
20 | const SAVE_CONFIG = clone(Config);
21 |
22 | Config.defaultStartDelay = 0;
23 |
24 | function cleanup() {
25 | try {
26 | rimraf.sync(Path.resolve("test/dist"));
27 | } catch (e) {
28 | //
29 | }
30 | }
31 |
32 | function generate(config, callback) {
33 | if (!callback) {
34 | callback = config;
35 | config = webpackConfig;
36 | }
37 | const compiler = webpack(config);
38 | compiler.run(function(err, stats) {
39 | stats.toString();
40 | callback(err);
41 | });
42 | }
43 |
44 | before(cleanup);
45 |
46 | after(() => {
47 | Object.assign(Config, SAVE_CONFIG);
48 | });
49 |
50 | const origLog = Object.assign({}, logger);
51 | let logs = [];
52 |
53 | beforeEach(function() {
54 | cleanup();
55 | logs = [];
56 | logger.log = function() {
57 | logs.push(Array.prototype.slice.apply(arguments).join(" "));
58 | };
59 | logger.error = logger.log;
60 | });
61 |
62 | afterEach(function() {
63 | cleanup();
64 | Object.assign(logger, origLog);
65 | });
66 |
67 | it(`should generate assets file @${tag}`, function() {
68 | function verify() {
69 | const isomorphicConfig = JSON.parse(
70 | Fs.readFileSync(Path.resolve("test/dist/isomorphic-assets.json"))
71 | );
72 | const expected = {
73 | valid: true,
74 | version: Pkg.version,
75 | timestamp: 0,
76 | context: "test/client",
77 | output: {
78 | path: "test/dist",
79 | filename: "bundle.js",
80 | publicPath: "/test/"
81 | },
82 | assets: {
83 | marked: {
84 | "test/client/images/smiley.jpg": "2029f1bb8dd109eb06f59157de62b529.jpg",
85 | "test/client/images/smiley2.jpg": "2029f1bb8dd109eb06f59157de62b529.jpg",
86 | "test/client/images/smiley.svg": "47869791f9dd9ef1be6e258e1a766ab8.svg",
87 | "test/client/images/smiley.png": "f958aee9742689b14418e8efef2b4032.png",
88 | "test/client/data/foo.bin": "71f74d0894d9ce89e22c678f0d8778b2.bin",
89 | "test/client/fonts/font.ttf": "1fb0e331c05a52d5eb847d6fc018320d.ttf",
90 | "test/nm/smiley2.jpg": "2029f1bb8dd109eb06f59157de62b529.jpg",
91 | "test/nm/demo.css": {
92 | "demo": "demo__demo"
93 | }
94 | },
95 | chunks: {
96 | main: [
97 | "main.style.css",
98 | "bundle.js"
99 | ]
100 | }
101 | }
102 | };
103 |
104 | isomorphicConfig.timestamp = 0;
105 | expect(isomorphicConfig).to.deep.equal(expected);
106 | }
107 |
108 | return asyncVerify(
109 | next => generate(next),
110 | () => verify()
111 | );
112 | });
113 |
114 | function verifyRequireAssets(publicPath) {
115 | publicPath = publicPath === undefined ? "/test/" : publicPath;
116 |
117 | const smiley = require("../client/images/smiley.jpg");
118 | const smiley2 = require("../client/images/smiley2.jpg");
119 | const smileyFull = require(Path.resolve("test/client/images/smiley.jpg"));
120 | const smileyPng = require("../client/images/smiley.png");
121 | const smileySvg = require("../client/images/smiley.svg");
122 | const fooBin = require("file-loader!isomorphic!../client/data/foo.bin");
123 | const expectedUrl = publicPath + "2029f1bb8dd109eb06f59157de62b529.jpg";
124 |
125 | expect(smiley).to.equal(expectedUrl);
126 | expect(smiley2).to.equal(expectedUrl);
127 | expect(smileyFull).to.equal(expectedUrl);
128 | expect(smileyPng).to.equal(publicPath + "f958aee9742689b14418e8efef2b4032.png");
129 | expect(smileySvg).to.equal(publicPath + "47869791f9dd9ef1be6e258e1a766ab8.svg");
130 | expect(fooBin).to.equal(publicPath + "71f74d0894d9ce89e22c678f0d8778b2.bin");
131 |
132 | try {
133 | require("bad_module");
134 | chai.assert(false, "expect exception");
135 | } catch (e) {
136 | expect(e).to.be.ok;
137 | }
138 |
139 | try {
140 | require("../client/images/smiley");
141 | chai.assert(false, "expect exception");
142 | } catch (e) {
143 | expect(e).to.be.ok;
144 | }
145 | }
146 |
147 | const testIsomorphicOutputFile = Path.resolve("test/dist/isomorphic-assets.json");
148 |
149 | it(`should load isomorphic config and extend require @${tag}`, function() {
150 | let isomorphicRequire;
151 | return asyncVerify(
152 | next => generate(next),
153 | () => {
154 | isomorphicRequire = extendRequire();
155 | isomorphicRequire.loadAssets([testIsomorphicOutputFile]);
156 | expect(isomorphicRequire.isWebpackDev()).to.equal(false);
157 | verifyRequireAssets();
158 | },
159 | runFinally(() => {
160 | isomorphicRequire && isomorphicRequire.reset();
161 | })
162 | );
163 | });
164 |
165 | it(`should call processConfig @${tag}`, function() {
166 | const sampleConfig = {
167 | valid: true,
168 | version: Pkg.version,
169 | timestamp: 0,
170 | context: "test/client",
171 | output: {
172 | path: "test/dist",
173 | filename: "bundle.js",
174 | publicPath: "/test/"
175 | },
176 | assets: {}
177 | };
178 | let isomorphicRequire;
179 | let testConfig;
180 | return asyncVerify(
181 | () => {
182 | isomorphicRequire = extendRequire(
183 | {
184 | processConfig: config => {
185 | return (testConfig = config);
186 | }
187 | },
188 | sampleConfig
189 | );
190 | expect(testConfig).to.deep.equal(sampleConfig);
191 | },
192 |
193 | runFinally(() => {
194 | isomorphicRequire && isomorphicRequire.reset();
195 | })
196 | );
197 | });
198 |
199 | it(`should default publicPath to "" @${tag}`, function() {
200 | const config = _.merge({}, webpackConfig);
201 | delete config.output.publicPath;
202 | return asyncVerify(
203 | next => {
204 | generate(config, next);
205 | },
206 | () => {
207 | const isomorphicConfig = JSON.parse(Fs.readFileSync(testIsomorphicOutputFile));
208 | expect(isomorphicConfig.output)
209 | .to.have.property("publicPath")
210 | .equal("");
211 | }
212 | );
213 | });
214 |
215 | it(`should fail if config version and package version mismatch @${tag}`, function() {
216 | const sampleConfig = {
217 | valid: true,
218 | version: "3.0.0",
219 | timestamp: 0,
220 | context: "test/client",
221 | output: {
222 | path: "test/dist",
223 | filename: "bundle.js",
224 | publicPath: "/test/"
225 | },
226 | assets: {}
227 | };
228 |
229 | return asyncVerify(
230 | expectError(() => {
231 | extendRequire({}, sampleConfig);
232 | }),
233 | error => {
234 | expect(error.message).contains("is different from config version 3.0.0");
235 | }
236 | );
237 | });
238 |
239 | it(`should fail if loadAssets got a config file with a mismatched version @${tag}`, () => {
240 | return asyncVerify(
241 | expectError(() => {
242 | extendRequire().loadAssets("test/bad-version-config.json");
243 | }),
244 | error => {
245 | expect(error.message).includes("is different from config version 3.0.1");
246 | }
247 | );
248 | });
249 |
250 | it(`extendRequire should avoid loading assets if WEBPACK_DEV env exist`, () => {
251 | process.env.WEBPACK_DEV = true;
252 | return asyncVerify(
253 | () => {
254 | // should not fail due to reading bad config file
255 | extendRequire({ assetsFile: "test/bad-version-config.json" });
256 | },
257 | runFinally(() => {
258 | delete process.env.WEBPACK_DEV;
259 | })
260 | );
261 | });
262 |
263 | it(`should help maintain a global extend require intance`, () => {
264 | setXRequire("blah");
265 | expect(getXRequire()).equal("blah");
266 | });
267 | };
268 |
--------------------------------------------------------------------------------
/test/lib/webpack-info.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const testInfo = {
4 | v4: {
5 | skip: false,
6 | xrequire: require,
7 | webpack: "webpack",
8 | devServer: "webpack-dev-server",
9 | config: require.resolve("../webpack4.config")
10 | },
11 | v3: {
12 | skip: true,
13 | xrequire: require("webpack3/xrequire"),
14 | webpack: "webpack",
15 | devServer: "webpack-dev-server",
16 | config: require.resolve("../webpack.config")
17 | }
18 | };
19 |
20 | module.exports = testInfo;
21 |
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require node_modules/electrode-archetype-njs-module-dev/config/test/setup.js
2 | --recursive
3 |
--------------------------------------------------------------------------------
/test/nm/demo.css:
--------------------------------------------------------------------------------
1 | .demo { background-color: greenyellow; }
--------------------------------------------------------------------------------
/test/nm/smiley2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchip/isomorphic-loader/f4e5fa18b778a517ebf7391e07092c9262d1c00c/test/nm/smiley2.jpg
--------------------------------------------------------------------------------
/test/spec/extend-require.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const { ExtendRequire } = require("../../lib/extend-require");
4 | const Pkg = require("../../package.json");
5 | const { asyncVerify, runFinally, expectError } = require("run-verify");
6 | const xstdout = require("xstdout");
7 |
8 | describe("extend-require", function() {
9 | it("should log error if require on non-path request is asset that doesn't exist", () => {
10 | const extendRequire = new ExtendRequire({});
11 | const intercept = xstdout.intercept(true);
12 |
13 | return asyncVerify(
14 | () => {
15 | extendRequire.initialize({
16 | version: Pkg.version,
17 | assets: {
18 | marked: {
19 | "blah-test": "test"
20 | }
21 | },
22 | output: {
23 | publicPath: ""
24 | }
25 | });
26 | },
27 | expectError(() => {
28 | require("blah-test");
29 | }),
30 | error => {
31 | expect(error.code).equal("MODULE_NOT_FOUND");
32 | expect(intercept.stderr.join("")).contains(
33 | `isomorphic-loader check asset blah-test exception`
34 | );
35 | },
36 | runFinally(() => {
37 | intercept.restore();
38 | extendRequire.reset();
39 | })
40 | );
41 | });
42 |
43 | it("should prepend publicPath from config", () => {
44 | const extendRequire = new ExtendRequire({});
45 |
46 | return asyncVerify(
47 | () => {
48 | extendRequire.initialize({
49 | version: Pkg.version,
50 | assets: {
51 | marked: {
52 | "test/nm/smiley2.jpg": "test.jpg"
53 | }
54 | },
55 | output: {
56 | publicPath: "/blah/"
57 | }
58 | });
59 | },
60 | () => require("../nm/smiley2.jpg"),
61 | assetUrl => {
62 | expect(assetUrl).equals("/blah/test.jpg");
63 | },
64 | runFinally(() => {
65 | extendRequire.reset();
66 | })
67 | );
68 | });
69 |
70 | it("should prepend custom publicPath", () => {
71 | const extendRequire = new ExtendRequire({});
72 |
73 | return asyncVerify(
74 | () => {
75 | extendRequire.initialize({
76 | version: Pkg.version,
77 | assets: {
78 | marked: {
79 | "test/nm/smiley2.jpg": "test.jpg"
80 | }
81 | },
82 | output: {
83 | publicPath: "/blah/"
84 | }
85 | });
86 | extendRequire.setPublicPath("/foo/");
87 | },
88 | () => require("../nm/smiley2.jpg"),
89 | assetUrl => {
90 | expect(assetUrl).equals("/foo/test.jpg");
91 | },
92 | runFinally(() => {
93 | extendRequire.reset();
94 | })
95 | );
96 | });
97 |
98 | it("should use custom url map", () => {
99 | const extendRequire = new ExtendRequire({});
100 |
101 | return asyncVerify(
102 | () => {
103 | extendRequire.initialize({
104 | version: Pkg.version,
105 | assets: {
106 | marked: {
107 | "test/nm/smiley2.jpg": "test.jpg"
108 | }
109 | },
110 | output: {
111 | publicPath: "/blah/"
112 | }
113 | });
114 | extendRequire.setUrlMapper(url => "/woop/" + url); // eslint-disable-line
115 | },
116 | () => require("../nm/smiley2.jpg"),
117 | assetUrl => {
118 | expect(assetUrl).equals("/woop/test.jpg");
119 | },
120 | runFinally(() => {
121 | extendRequire.reset();
122 | })
123 | );
124 | });
125 |
126 | it("should log error urlMap throws", () => {
127 | const extendRequire = new ExtendRequire({});
128 | const intercept = xstdout.intercept(true);
129 |
130 | return asyncVerify(
131 | () => {
132 | extendRequire.initialize({
133 | version: Pkg.version,
134 | assets: {
135 | marked: {
136 | "test/nm/smiley2.jpg": "test.jpg"
137 | }
138 | },
139 | output: {
140 | publicPath: ""
141 | }
142 | });
143 | extendRequire.setUrlMapper(() => {
144 | throw new Error("test oops");
145 | });
146 | },
147 | () => require("../nm/smiley2.jpg"),
148 | assetUrl => {
149 | intercept.restore();
150 | expect(assetUrl).equals("test.jpg");
151 |
152 | expect(intercept.stderr.join("")).contains("urlMap thrown error Error: test oops");
153 | },
154 | runFinally(() => {
155 | intercept.restore();
156 | extendRequire.reset();
157 | })
158 | );
159 | });
160 |
161 | it("should return object for css", () => {
162 | const extendRequire = new ExtendRequire({});
163 |
164 | return asyncVerify(
165 | () => {
166 | extendRequire.initialize({
167 | version: Pkg.version,
168 | assets: {
169 | marked: {
170 | "test/nm/demo.css": { demo: "abc" }
171 | }
172 | },
173 | output: {
174 | publicPath: "/blah/"
175 | }
176 | });
177 | },
178 | () => require("../nm/demo.css"),
179 | assetUrl => {
180 | expect(assetUrl).deep.equals({ demo: "abc" });
181 | },
182 | runFinally(() => {
183 | extendRequire.reset();
184 | })
185 | );
186 | });
187 |
188 | it("should intercept by extensions", () => {
189 | const extendRequire = new ExtendRequire({
190 | ignoreExtensions: ".css"
191 | });
192 |
193 | return asyncVerify(
194 | () => require("blah.css"),
195 | asset => {
196 | expect(asset).deep.equals({});
197 | },
198 | expectError(() => require("blah.jpg")),
199 | error => {
200 | expect(error.message).contains("Cannot find module 'blah.jpg");
201 | },
202 | runFinally(() => {
203 | extendRequire.reset();
204 | })
205 | );
206 | });
207 |
208 | it("should be ok to reset before initializing", () => {
209 | const extendRequire = new ExtendRequire();
210 | extendRequire.reset();
211 | // no error => good
212 | });
213 | });
214 |
--------------------------------------------------------------------------------
/test/spec/isomorphic.dev.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const clone = require("clone");
4 | const webpackInfo = require("../lib/webpack-info");
5 |
6 | Object.keys(webpackInfo).forEach(ver => {
7 | const info = webpackInfo[ver];
8 | if (info.skip) return;
9 |
10 | const xrequire = info.xrequire;
11 | const webpack = xrequire(info.webpack);
12 | const WebpackDevServer = xrequire(info.devServer);
13 | const webpackConfig = clone(xrequire(info.config));
14 |
15 | require("../lib/isomorphic.dev.spec")({
16 | title: `isomorphic extend with webpack ${ver} & webpack-dev-server`,
17 | tag: `webpack_${ver}`,
18 | timeout: 10000,
19 | webpack,
20 | WebpackDevServer,
21 | webpackConfig
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/spec/isomorphic.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const clone = require("clone");
4 | const webpackInfo = require("../lib/webpack-info");
5 |
6 | Object.keys(webpackInfo).forEach(ver => {
7 | const info = webpackInfo[ver];
8 | if (info.skip) return;
9 | describe(`isomorphic extend with webpack ${ver}`, function() {
10 | this.timeout(20000);
11 | const webpack = info.xrequire(info.webpack);
12 | const webpackConfig = clone(info.xrequire(info.config));
13 | require("../lib/isomorphic.spec")({
14 | tag: `webpack_${ver}`,
15 | webpack,
16 | webpackConfig
17 | });
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/test/spec/loader.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const isomorphicLoader = require("../../lib/isomorphic-loader");
4 | const CSS_MODULE_MAPPINGS = Symbol.for("css-module-content-mapping");
5 |
6 | describe("isomorphic-loader", function() {
7 | it("should return the content as such", () => {
8 | const boundFn = isomorphicLoader.bind({ _module: { userRequest: "" } });
9 | const content = boundFn("testData");
10 | expect(content).to.equal("testData");
11 | });
12 |
13 | it("should add content mapping for .(css|less|styl|sass|scss) files", () => {
14 | const boundFn = isomorphicLoader.bind({ _module: { userRequest: "test.css" } });
15 | const content = boundFn("testData");
16 | expect(content).to.equal("testData");
17 | expect(global[CSS_MODULE_MAPPINGS]["test.css"]).to.equal("testData");
18 | });
19 |
20 | it("should not add mapping for unsupported filetypes", () => {
21 | const boundFn = isomorphicLoader.bind({ _module: { userRequest: "foo.js" } });
22 | const content = boundFn("testData");
23 | expect(content).to.equal("testData");
24 | expect(global[CSS_MODULE_MAPPINGS]["foo.js"]).to.be.undefined;
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/test/spec/logger.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const logger = require("../../lib/logger");
4 | const xstdout = require("xstdout");
5 |
6 | describe("logger", function() {
7 | after(() => {
8 | logger.setLevel();
9 | });
10 |
11 | it("should handle log info", () => {
12 | const intercept = xstdout.intercept(true);
13 |
14 | logger.log("blah");
15 | const a = intercept.stdout.join("").trim();
16 | intercept.stdout = [];
17 | logger.setLevel("error");
18 | logger.log("oops");
19 | const b = intercept.stdout.join("").trim();
20 | intercept.restore();
21 | expect(a).equal("blah");
22 | expect(b).equal("");
23 | });
24 |
25 | it("should handle log error", () => {
26 | const intercept = xstdout.intercept(true);
27 |
28 | logger.setLevel();
29 | logger.log("blah");
30 | logger.error("error");
31 | const a = intercept.stdout.join("").trim() + intercept.stderr.join("").trim();
32 | intercept.stdout.splice(0, intercept.stdout.length);
33 | intercept.stderr.splice(0, intercept.stderr.length);
34 | logger.setLevel("error");
35 | logger.log("oops");
36 | logger.error("error");
37 | logger.setLevel("none");
38 | logger.error("none");
39 | const b = intercept.stdout.join("").trim() + intercept.stderr.join("").trim();
40 | intercept.restore();
41 | expect(a).equal("blaherror");
42 | expect(b).equal("error");
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/test/spec/utils.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const Path = require("path");
4 | const utils = require("../../lib/utils");
5 |
6 | describe("utils", function() {
7 | describe("getParentPath", function() {
8 | it("should return path if it exist", () => {
9 | expect(utils.getParentPath({ path: "/test" })).to.equal("/test");
10 | });
11 |
12 | it("should return dirname of filename if only that exist", () => {
13 | expect(utils.getParentPath({ filename: "/test/foo.txt" })).to.equal("/test");
14 | });
15 |
16 | it(`should return "" if nothing exist`, () => {
17 | expect(utils.getParentPath()).to.equal("");
18 | expect(utils.getParentPath({})).to.equal("");
19 | });
20 | });
21 |
22 | describe("getWebpackRequest", function() {
23 | it("should return userRequest", () => {
24 | expect(utils.getWebpackRequest({ reasons: [{ userRequest: "test" }] })).to.equal("test");
25 | });
26 |
27 | it(`should return "" if can't find userRequest`, () => {
28 | expect(utils.getWebpackRequest()).to.equal("");
29 | expect(utils.getWebpackRequest({})).to.equal("");
30 | expect(utils.getWebpackRequest({ reasons: [] })).to.equal("");
31 | });
32 | });
33 |
34 | describe("replaceAppSrcDir", function() {
35 | it("should return request without appSrcDir", () => {
36 | expect(utils.replaceAppSrcDir("test")).to.equal("test");
37 | });
38 |
39 | it("should return request if appSrcDir not match", () => {
40 | expect(utils.replaceAppSrcDir("test/hello", "src")).to.equal("test/hello");
41 | });
42 |
43 | it("should replace request with appSrcDir if match", () => {
44 | expect(utils.replaceAppSrcDir("test/hello", "test")).to.equal("$AppSrc/hello");
45 | expect(utils.replaceAppSrcDir("test/hello", "test/")).to.equal("$AppSrc/hello");
46 | });
47 | });
48 |
49 | describe("removeCwd", function() {
50 | it("should remove process.cwd() from path", () => {
51 | expect(utils.removeCwd(Path.resolve("test"))).to.equal("test");
52 | });
53 | });
54 |
55 | describe("requireFromString", function() {
56 | it("should load module from string", () => {
57 | const result = utils.requireFromString("module.exports = { test: 'abc'}");
58 | expect(result).to.deep.equal({ test: "abc" });
59 | });
60 | });
61 |
62 | describe("getMyNodeModulesPath", function() {
63 | it("should find simple path", () => {
64 | expect(utils.getMyNodeModulesPath("/test/node_modules/isomorphic-loader/lib")).equals(
65 | "isomorphic-loader/lib"
66 | );
67 | });
68 |
69 | it("should return up to 4 parts path if dir doesn't contain name", () => {
70 | expect(utils.getMyNodeModulesPath("/test/node_modules/123456789/lib")).equals(
71 | "test/node_modules/123456789/lib"
72 | );
73 | expect(utils.getMyNodeModulesPath("/foo/blah/test/node_modules/123456789/lib")).equals(
74 | "test/node_modules/123456789/lib"
75 | );
76 | expect(utils.getMyNodeModulesPath("/foo/123456789/lib")).equals("/foo/123456789/lib");
77 | });
78 |
79 | it("should find path with extras between name", () => {
80 | expect(utils.getMyNodeModulesPath("/test/node_modules/isomorphic-loader/123/lib")).equals(
81 | "isomorphic-loader/123/lib"
82 | );
83 | });
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/test/webpack.config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const { IsomorphicLoaderPlugin } = require("..");
4 | const Path = require("path");
5 |
6 | module.exports = {
7 | context: Path.resolve("test/client"),
8 | entry: "./entry.js",
9 | output: {
10 | path: Path.resolve("test/dist"),
11 | filename: "bundle.js",
12 | publicPath: "/test/"
13 | },
14 | plugins: [new IsomorphicLoaderPlugin({ webpackDev: { addUrl: false } })],
15 | module: {
16 | rules: [
17 | {
18 | test: /\.(jpe?g|png|gif|svg)$/i,
19 | use: [{ loader: "electrode-cdn-file-loader", options: { limit: 10000 } }, "../.."]
20 | },
21 | {
22 | test: /\.(ttf|eot)$/,
23 | use: [{ loader: "electrode-cdn-file-loader", options: { limit: 10000 } }, "../.."]
24 | }
25 | ]
26 | },
27 | resolve: {
28 | alias: {
29 | smiley2Jpg: Path.join(__dirname, "./nm/smiley2.jpg")
30 | }
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/test/webpack3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack3",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {},
7 | "dependencies": {
8 | "webpack": "^3",
9 | "webpack-dev-server": "^2"
10 | },
11 | "private": true
12 | }
13 |
--------------------------------------------------------------------------------
/test/webpack3/xrequire.js:
--------------------------------------------------------------------------------
1 | module.exports = require;
2 |
--------------------------------------------------------------------------------
/test/webpack4.config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const { IsomorphicLoaderPlugin } = require("..");
4 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
5 | const Path = require("path");
6 |
7 | module.exports = {
8 | context: Path.resolve("test/client"),
9 | entry: "./entry.js",
10 | output: {
11 | path: Path.resolve("test/dist"),
12 | filename: "bundle.js",
13 | publicPath: "/test/"
14 | },
15 | plugins: [
16 | new IsomorphicLoaderPlugin({ webpackDev: { addUrl: false } }),
17 | new MiniCssExtractPlugin({ filename: "[name].style.css" })
18 | ],
19 | module: {
20 | rules: [
21 | {
22 | test: /\.(jpe?g|png|gif|svg)$/i,
23 | loader: "file-loader!../.."
24 | },
25 | {
26 | test: /\.(ttf|eot)$/,
27 | loader: "file-loader!../.."
28 | },
29 | {
30 | test: /\.css$/,
31 | use: [
32 | "../..",
33 | {
34 | loader: MiniCssExtractPlugin.loader,
35 | options: {
36 | publicPath: "",
37 | esModule: false,
38 | modules: true
39 | }
40 | },
41 | {
42 | loader: "css-loader",
43 | options: {
44 | context: Path.resolve("test/client"),
45 | modules: true,
46 | localIdentName: "[name]__[local]"
47 | }
48 | }
49 | ]
50 | }
51 | ]
52 | },
53 | resolve: {
54 | alias: {
55 | smiley2Jpg: Path.join(__dirname, "./nm/smiley2.jpg"),
56 | demoCss: Path.join(__dirname, "./nm/demo.css")
57 | }
58 | }
59 | };
60 |
--------------------------------------------------------------------------------
/xclap.js:
--------------------------------------------------------------------------------
1 | require("electrode-archetype-njs-module-dev")();
2 |
--------------------------------------------------------------------------------