fix(core): resolve webpack loaders with require.resolve() (#3341)

With strict package managers such as pnpm or Yarn PnP, transitive
dependencies are *not* hoisted to the root node_modules folder. This
means that a webpack config defined within a package like
'@nrwl/cypress' cannot resolve loaders like 'ts-loader', unless
'ts-loader' is declared in the workspace's own package.json.

This is a problem because the workspace might define a different version
of 'ts-loader', incompatible with the version declared by
'@nrwl/cypress/package.json'. The workspace should not need to declare
a dependency on 'ts-loader' anyway.

See also:
* https://github.com/pnpm/pnpm/issues/801
* https://github.com/webpack/webpack/issues/5087
This commit is contained in:
Spencer Elliott 2020-07-15 13:55:38 -04:00 committed by GitHub
parent 24f544100e
commit d74ab4e9d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 35 additions and 31 deletions

View File

@ -20,7 +20,7 @@ describe('getWebpackConfig', () => {
}); });
expect(config.module.rules).toContainEqual({ expect(config.module.rules).toContainEqual({
test: /\.(j|t)sx?$/, test: /\.(j|t)sx?$/,
loader: 'ts-loader', loader: require.resolve('ts-loader'),
exclude: [/node_modules/], exclude: [/node_modules/],
options: { options: {
configFile: './tsconfig.json', configFile: './tsconfig.json',

View File

@ -37,7 +37,7 @@ export function getWebpackConfig(config: any) {
rules: [ rules: [
{ {
test: /\.(j|t)sx?$/, test: /\.(j|t)sx?$/,
loader: 'ts-loader', loader: require.resolve('ts-loader'),
exclude: [/node_modules/], exclude: [/node_modules/],
options: { options: {
configFile: config.env.tsConfig, configFile: config.env.tsConfig,

View File

@ -58,7 +58,7 @@ export function createWebpackConfig(
use: [ use: [
'@svgr/webpack?-svgo,+titleProp,+ref![path]', '@svgr/webpack?-svgo,+titleProp,+ref![path]',
{ {
loader: 'url-loader', loader: require.resolve('url-loader'),
options: { options: {
limit: 10000, // 10kB limit: 10000, // 10kB
name: '[name].[hash:7].[ext]', name: '[name].[hash:7].[ext]',
@ -70,7 +70,7 @@ export function createWebpackConfig(
{ {
use: [ use: [
{ {
loader: 'url-loader', loader: require.resolve('url-loader'),
options: { options: {
limit: 10000, // 10kB limit: 10000, // 10kB
name: '[name].[hash:7].[ext]', name: '[name].[hash:7].[ext]',

View File

@ -47,7 +47,7 @@ describe('getBaseWebpackPartial', () => {
); );
expect(typescriptRule).toBeTruthy(); expect(typescriptRule).toBeTruthy();
expect(typescriptRule.loader).toEqual('ts-loader'); expect(typescriptRule.loader).toContain('ts-loader');
}); });
it('should split typescript type checking into a separate workers', () => { it('should split typescript type checking into a separate workers', () => {
@ -131,7 +131,9 @@ describe('getBaseWebpackPartial', () => {
const result = getBaseWebpackPartial(input); const result = getBaseWebpackPartial(input);
expect( expect(
result.module.rules.find((rule) => rule.loader === 'ts-loader').options result.module.rules.find((rule) =>
rule.loader.toString().includes('ts-loader')
).options
).toEqual({ ).toEqual({
configFile: 'tsconfig.json', configFile: 'tsconfig.json',
transpileOnly: true, transpileOnly: true,

View File

@ -36,7 +36,7 @@ export function getBaseWebpackPartial(
rules: [ rules: [
{ {
test: /\.(j|t)sx?$/, test: /\.(j|t)sx?$/,
loader: `ts-loader`, loader: require.resolve(`ts-loader`),
exclude: /node_modules/, exclude: /node_modules/,
options: { options: {
configFile: options.tsConfig, configFile: options.tsConfig,

View File

@ -5,7 +5,7 @@ function getWebpackConfig(config: Configuration) {
config.module.rules.push( config.module.rules.push(
{ {
test: /\.(png|jpe?g|gif|webp)$/, test: /\.(png|jpe?g|gif|webp)$/,
loader: 'url-loader', loader: require.resolve('url-loader'),
options: { options: {
limit: 10000, // 10kB limit: 10000, // 10kB
name: '[name].[hash:7].[ext]', name: '[name].[hash:7].[ext]',
@ -22,7 +22,7 @@ function getWebpackConfig(config: Configuration) {
use: [ use: [
'@svgr/webpack?-svgo,+titleProp,+ref![path]', '@svgr/webpack?-svgo,+titleProp,+ref![path]',
{ {
loader: 'url-loader', loader: require.resolve('url-loader'),
options: { options: {
limit: 10000, // 10kB limit: 10000, // 10kB
name: '[name].[hash:7].[ext]', name: '[name].[hash:7].[ext]',
@ -34,7 +34,7 @@ function getWebpackConfig(config: Configuration) {
{ {
use: [ use: [
{ {
loader: 'url-loader', loader: require.resolve('url-loader'),
options: { options: {
limit: 10000, // 10kB limit: 10000, // 10kB
name: '[name].[hash:7].[ext]', name: '[name].[hash:7].[ext]',

View File

@ -49,7 +49,7 @@ describe('getBaseWebpackPartial', () => {
); );
expect(rule).toBeTruthy(); expect(rule).toBeTruthy();
expect(rule.loader).toEqual('babel-loader'); expect(rule.loader).toContain('babel-loader');
}); });
it('should split typescript type checking into a separate workers', () => { it('should split typescript type checking into a separate workers', () => {
@ -165,8 +165,9 @@ describe('getBaseWebpackPartial', () => {
const result = getBaseWebpackPartial(input, true); const result = getBaseWebpackPartial(input, true);
expect( expect(
(result.module.rules.find((rule) => rule.loader === 'babel-loader') (result.module.rules.find((rule) =>
.options as any).envName rule.loader.toString().includes('babel-loader')
).options as any).envName
).toMatch('modern'); ).toMatch('modern');
}); });
@ -174,8 +175,9 @@ describe('getBaseWebpackPartial', () => {
const result = getBaseWebpackPartial(input, false); const result = getBaseWebpackPartial(input, false);
expect( expect(
(result.module.rules.find((rule) => rule.loader === 'babel-loader') (result.module.rules.find((rule) =>
.options as any).envName rule.loader.toString().includes('babel-loader')
).options as any).envName
).toMatch('legacy'); ).toMatch('legacy');
}); });
}); });

View File

@ -48,7 +48,7 @@ export function getBaseWebpackPartial(
rules: [ rules: [
{ {
test: /\.([jt])sx?$/, test: /\.([jt])sx?$/,
loader: `babel-loader`, loader: require.resolve(`babel-loader`),
exclude: /node_modules/, exclude: /node_modules/,
options: { options: {
rootMode: 'upward', rootMode: 'upward',

View File

@ -316,7 +316,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
sourceMapUseRule = { sourceMapUseRule = {
use: [ use: [
{ {
loader: 'source-map-loader', loader: require.resolve('source-map-loader'),
}, },
], ],
}; };
@ -490,7 +490,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
rules: [ rules: [
{ {
test: /\.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/, test: /\.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/,
loader: 'file-loader', loader: require.resolve('file-loader'),
options: { options: {
name: `[name]${hashFormat.file}.[ext]`, name: `[name]${hashFormat.file}.[ext]`,
}, },

View File

@ -146,7 +146,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
test: /\.scss$|\.sass$/, test: /\.scss$|\.sass$/,
use: [ use: [
{ {
loader: 'sass-loader', loader: require.resolve('sass-loader'),
options: { options: {
implementation: sassImplementation, implementation: sassImplementation,
sourceMap: cssSourceMap, sourceMap: cssSourceMap,
@ -164,7 +164,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
test: /\.less$/, test: /\.less$/,
use: [ use: [
{ {
loader: 'less-loader', loader: require.resolve('less-loader'),
options: { options: {
sourceMap: cssSourceMap, sourceMap: cssSourceMap,
javascriptEnabled: true, javascriptEnabled: true,
@ -177,7 +177,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
test: /\.styl$/, test: /\.styl$/,
use: [ use: [
{ {
loader: 'stylus-loader', loader: require.resolve('stylus-loader'),
options: { options: {
sourceMap: cssSourceMap, sourceMap: cssSourceMap,
paths: includePaths, paths: includePaths,
@ -192,9 +192,9 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
exclude: globalStylePaths, exclude: globalStylePaths,
test, test,
use: [ use: [
{ loader: 'raw-loader' }, { loader: require.resolve('raw-loader') },
{ {
loader: 'postcss-loader', loader: require.resolve('postcss-loader'),
options: { options: {
ident: 'embedded', ident: 'embedded',
plugins: postcssPluginCreator, plugins: postcssPluginCreator,
@ -224,10 +224,10 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
use: [ use: [
buildOptions.extractCss buildOptions.extractCss
? MiniCssExtractPlugin.loader ? MiniCssExtractPlugin.loader
: 'style-loader', : require.resolve('style-loader'),
RawCssLoader, RawCssLoader,
{ {
loader: 'postcss-loader', loader: require.resolve('postcss-loader'),
options: { options: {
ident: buildOptions.extractCss ? 'extracted' : 'embedded', ident: buildOptions.extractCss ? 'extracted' : 'embedded',
plugins: postcssPluginCreator, plugins: postcssPluginCreator,

View File

@ -121,7 +121,7 @@ function getStylesPartial(
loaderConfig.loader === 'raw-loader' loaderConfig.loader === 'raw-loader'
) { ) {
return { return {
loader: 'style-loader', loader: require.resolve('style-loader'),
}; };
} }
return loaderConfig; return loaderConfig;
@ -138,10 +138,10 @@ function getStylesPartial(
{ {
loader: options.extractCss loader: options.extractCss
? MiniCssExtractPlugin.loader ? MiniCssExtractPlugin.loader
: 'style-loader', : require.resolve('style-loader'),
}, },
{ {
loader: 'css-loader', loader: require.resolve('css-loader'),
options: { options: {
modules: true, modules: true,
importLoaders: 1, importLoaders: 1,
@ -155,16 +155,16 @@ function getStylesPartial(
{ {
loader: options.extractCss loader: options.extractCss
? MiniCssExtractPlugin.loader ? MiniCssExtractPlugin.loader
: 'style-loader', : require.resolve('style-loader'),
}, },
{ {
loader: 'css-loader', loader: require.resolve('css-loader'),
options: { options: {
modules: true, modules: true,
importLoaders: 1, importLoaders: 1,
}, },
}, },
{ loader: 'sass-loader' }, { loader: require.resolve('sass-loader') },
], ],
}, },
...rules, ...rules,