UNPKG

6.04 kBJavaScriptView Raw
1const path = require('path');
2const util = require('util');
3
4const PATH_DELIMITER = '[\\\\/]'; // match 2 antislashes or one slash
5
6/**
7 * Stolen from https://stackoverflow.com/questions/10776600/testing-for-equality-of-regular-expressions
8 */
9const regexEqual = (x, y) => {
10 return (
11 x instanceof RegExp &&
12 y instanceof RegExp &&
13 x.source === y.source &&
14 x.global === y.global &&
15 x.ignoreCase === y.ignoreCase &&
16 x.multiline === y.multiline
17 );
18};
19
20const generateIncludes = (modules) => {
21 return [
22 new RegExp(`(${modules.map(safePath).join('|')})$`),
23 new RegExp(`(${modules.map(safePath).join('|')})${PATH_DELIMITER}(?!.*node_modules)`)
24 ];
25};
26
27const generateExcludes = (modules) => {
28 return [
29 new RegExp(
30 `node_modules${PATH_DELIMITER}(?!(${modules.map(safePath).join('|')})(${PATH_DELIMITER}|$)(?!.*node_modules))`
31 )
32 ];
33};
34
35/**
36 * On Windows, the Regex won't match as Webpack tries to resolve the
37 * paths of the modules. So we need to check for \\ and /
38 */
39const safePath = (module) => module.split('/').join(PATH_DELIMITER);
40
41/**
42 * Actual Next.js plugin
43 */
44const withTmInitializer = (transpileModules = []) => {
45 const withTM = (nextConfig = {}) => {
46 if (transpileModules.length === 0) return nextConfig;
47
48 const includes = generateIncludes(transpileModules);
49 const excludes = generateExcludes(transpileModules);
50
51 return Object.assign({}, nextConfig, {
52 webpack(config, options) {
53 // Safecheck for Next < 5.0
54 if (!options.defaultLoaders) {
55 throw new Error(
56 'This plugin is not compatible with Next.js versions below 5.0.0 https://err.sh/next-plugins/upgrade'
57 );
58 }
59
60 // Avoid Webpack to resolve transpiled modules path to their real path as
61 // we want to test modules from node_modules only. If it was enabled,
62 // modules in node_modules installed via symlink would then not be
63 // transpiled.
64 config.resolve.symlinks = false;
65
66 // Since Next.js 8.1.0, config.externals is undefined
67 if (config.externals) {
68 config.externals = config.externals.map((external) => {
69 if (typeof external !== 'function') return external;
70 return (ctx, req, cb) => {
71 return includes.find((include) =>
72 req.startsWith('.') ? include.test(path.resolve(ctx, req)) : include.test(req)
73 )
74 ? cb()
75 : external(ctx, req, cb);
76 };
77 });
78 }
79
80 // Add a rule to include and parse all modules (js & ts)
81 config.module.rules.push({
82 test: /\.+(js|jsx|ts|tsx)$/,
83 loader: options.defaultLoaders.babel,
84 include: includes
85 });
86
87 // Support CSS modules + global in node_modules
88 // TODO ask Next.js maintainer to expose the css-loader via defaultLoaders
89 const nextCssLoaders = config.module.rules.find((rule) => typeof rule.oneOf === 'object');
90
91 // .module.css
92 if (nextCssLoaders) {
93 const nextCssLoader = nextCssLoaders.oneOf.find(
94 (rule) => rule.sideEffects === false && regexEqual(rule.test, /\.module\.css$/)
95 );
96
97 const nextSassLoader = nextCssLoaders.oneOf.find(
98 (rule) => rule.sideEffects === false && regexEqual(rule.test, /\.module\.(scss|sass)$/)
99 );
100
101 if (nextCssLoader) {
102 nextCssLoader.issuer.include = nextCssLoader.issuer.include.concat(includes);
103 nextCssLoader.issuer.exclude = excludes;
104 }
105
106 if (nextSassLoader) {
107 nextSassLoader.issuer.include = nextCssLoader.issuer.include.concat(includes);
108 nextSassLoader.issuer.exclude = excludes;
109 }
110
111 // Hack our way to disable errors on node_modules CSS modules
112 const nextErrorCssModuleLoader = nextCssLoaders.oneOf.find(
113 (rule) =>
114 rule.use &&
115 rule.use.loader === 'error-loader' &&
116 rule.use.options &&
117 rule.use.options.reason ===
118 'CSS Modules \u001b[1mcannot\u001b[22m be imported from within \u001b[1mnode_modules\u001b[22m.\n' +
119 'Read more: https://err.sh/next.js/css-modules-npm'
120 );
121
122 if (nextErrorCssModuleLoader) {
123 nextErrorCssModuleLoader.exclude = includes;
124 }
125
126 const nextErrorCssGlobalLoader = nextCssLoaders.oneOf.find(
127 (rule) =>
128 rule.use &&
129 rule.use.loader === 'error-loader' &&
130 rule.use.options &&
131 rule.use.options.reason ===
132 'Global CSS \u001b[1mcannot\u001b[22m be imported from within \u001b[1mnode_modules\u001b[22m.\n' +
133 'Read more: https://err.sh/next.js/css-npm'
134 );
135
136 if (nextErrorCssGlobalLoader) {
137 nextErrorCssGlobalLoader.exclude = includes;
138 }
139 }
140
141 // Overload the Webpack config if it was already overloaded
142 if (typeof nextConfig.webpack === 'function') {
143 return nextConfig.webpack(config, options);
144 }
145
146 return config;
147 },
148
149 // webpackDevMiddleware needs to be told to watch the changes in the
150 // transpiled modules directories
151 webpackDevMiddleware(config) {
152 // Replace /node_modules/ by the new exclude RegExp (including the modules
153 // that are going to be transpiled)
154 // https://github.com/zeit/next.js/blob/815f2e91386a0cd046c63cbec06e4666cff85971/packages/next/server/hot-reloader.js#L335
155 const ignored = config.watchOptions.ignored
156 .filter((regexp) => !regexEqual(regexp, /[\\/]node_modules[\\/]/))
157 .concat(excludes);
158
159 config.watchOptions.ignored = ignored;
160
161 if (typeof nextConfig.webpackDevMiddleware === 'function') {
162 return nextConfig.webpackDevMiddleware(config);
163 }
164
165 return config;
166 }
167 });
168 };
169
170 return withTM;
171};
172
173module.exports = withTmInitializer;