diff --git a/scopes/compilation/bundler/dev-server.service.tsx b/scopes/compilation/bundler/dev-server.service.tsx index fe0b93b54697..f7e033714126 100644 --- a/scopes/compilation/bundler/dev-server.service.tsx +++ b/scopes/compilation/bundler/dev-server.service.tsx @@ -56,9 +56,6 @@ export class DevServerService implements EnvService { - describe('should work with empty array', () => { - it('include any package in the node_modules', () => { - expect(generateNodeModulesPattern({ packages: [] })).toEqual('node_modules/(?!()/)'); + describe('default format for JEST', () => { + describe('should work with empty array', () => { + it('include any package in the node_modules', () => { + expect(generateNodeModulesPattern({ packages: [] })).toEqual('node_modules/(?!()/)'); + }); }); - }); - describe('should work with one package', () => { - let pattern; - let regex; - beforeAll(() => { - pattern = generateNodeModulesPattern({ packages: ['@myorg'] }); - regex = new RegExp(pattern); + describe('should work with one package', () => { + let pattern; + let regex; + beforeAll(() => { + pattern = generateNodeModulesPattern({ packages: ['@myorg'] }); + regex = new RegExp(pattern); + }); + + describe('should exclude the package', () => { + it('should have yarn structure excluded', () => { + expect(regex.test('node_modules/@myorg/something')).toBeFalsy(); + }); + it('should have new pnpm structure with + excluded', () => { + expect(regex.test('node_modules/.pnpm/registry.npmjs.org+@myorg+something/')).toBeFalsy(); + }); + it('should have new pnpm structure excluded', () => { + expect(regex.test('node_modules/.pnpm/@myorg+something/')).toBeFalsy(); + }); + }); }); + describe('should work with more than one packages', () => { + let packagesToTransform; + let pattern; + let regex; + beforeAll(() => { + packagesToTransform = ['react', '@myorg', 'testing-library__dom']; + pattern = generateNodeModulesPattern({ packages: packagesToTransform }); + regex = new RegExp(pattern); + }); - describe('should exclude the package', () => { - it('should have yarn structure excluded', () => { - expect(regex.test('node_modules/@myorg/something')).toBeFalsy(); + describe('should exclude the first package', () => { + it('should have yarn structure excluded', () => { + expect(regex.test('node_modules/react/something')).toBeFalsy(); + }); + it('should have new pnpm structure with + excluded', () => { + expect(regex.test('node_modules/.pnpm/registry.npmjs.org+react/something')).toBeFalsy(); + }); + it('should have new pnpm structure excluded', () => { + expect(regex.test('node_modules/.pnpm/react/something')).toBeFalsy(); + }); }); - it('should have new pnpm structure with + excluded', () => { - expect(regex.test('node_modules/.pnpm/registry.npmjs.org+@myorg+something/')).toBeFalsy(); + describe('should exclude the second package', () => { + it('should have yarn structure excluded', () => { + expect(regex.test('node_modules/@myorg/something')).toBeFalsy(); + }); + it('should have new pnpm structure with + excluded', () => { + expect(regex.test('node_modules/.pnpm/registry.npmjs.org+@myorg+something/')).toBeFalsy(); + }); + it('should have new pnpm structure excluded', () => { + expect(regex.test('node_modules/.pnpm/@myorg+something/')).toBeFalsy(); + }); }); - it('should have new pnpm structure excluded', () => { - expect(regex.test('node_modules/.pnpm/@myorg+something/')).toBeFalsy(); + describe('should exclude the third package', () => { + it('should have yarn structure excluded', () => { + expect(regex.test('node_modules/testing-library__dom/something')).toBeFalsy(); + }); + it('should have new pnpm structure with + excluded', () => { + expect(regex.test('node_modules/.pnpm/registry.npmjs.org+testing-library__dom/something')).toBeFalsy(); + }); + it('should have new pnpm structure excluded', () => { + expect(regex.test('node_modules/.pnpm/testing-library__dom/something')).toBeFalsy(); + }); }); }); - }); - describe('should work with more than one packages', () => { - let packagesToTransform; - let pattern; - let regex; - beforeAll(() => { - packagesToTransform = ['react', '@myorg', 'testing-library__dom']; - pattern = generateNodeModulesPattern({ packages: packagesToTransform }); - regex = new RegExp(pattern); - }); + describe('should not exclude the package when is not in the regex', () => { + let packagesToTransform; + let pattern; + let regex; + beforeAll(() => { + packagesToTransform = ['react']; + pattern = generateNodeModulesPattern({ packages: packagesToTransform }); + regex = new RegExp(pattern); + }); - describe('should exclude the first package', () => { - it('should have yarn structure excluded', () => { - expect(regex.test('node_modules/react/something')).toBeFalsy(); + describe('should not exclude', () => { + it('with yarn structure', () => { + expect(regex.test('node_modules/not-excluded-package/some-path')).toBeTruthy(); + }); + it('with old pnpm structure', () => { + expect(regex.test('node_modules/.pnpm/registry.npmjs.org/not-excluded-package/some-path')).toBeTruthy(); + }); + it("with new pnpm structure with '+'", () => { + expect(regex.test('node_modules/.pnpm/registry.npmjs.org+not-excluded-package/some-path')).toBeTruthy(); + }); + it('with old pnpm structure, different registry name', () => { + expect( + regex.test('node_modules/.pnpm/registry.artifactory.something/not-excluded-package/some-path') + ).toBeTruthy(); + }); + it("with new pnpm structure with '+'", () => { + expect( + regex.test('node_modules/.pnpm/registry.artifactory.something+not-excluded-package/some-path') + ).toBeTruthy(); + }); + it('with new pnpm structure', () => { + expect(regex.test('node_modules/.pnpm/not-excluded-package/some-path')).toBeTruthy(); + }); + }); + }); + describe('should exclude components', () => { + let pattern; + let regex; + beforeAll(() => { + pattern = generateNodeModulesPattern({ excludeComponents: true }); + regex = new RegExp(pattern); }); - it('should have new pnpm structure with + excluded', () => { - expect(regex.test('node_modules/.pnpm/registry.npmjs.org+react/something')).toBeFalsy(); + const fixtures = [ + [true, 'not-a-component'], + [true, '@myorg/not-a-component'], + [true, 'scope.comp-name'], + [false, '@myorg/scope.comp-name'], + [false, '@myorg/scope.namespace.comp-name'], + [false, '@myorg/scope.ns1.ns2.comp-name'], + ]; + // @ts-ignore + it.each(fixtures)(`should return %s for %s in yarn node_modules`, (expectedResult: boolean, pkgName: string) => { + expect(regex.test(`node_modules/${pkgName}/`)).toEqual(expectedResult); }); - it('should have new pnpm structure excluded', () => { - expect(regex.test('node_modules/.pnpm/react/something')).toBeFalsy(); + // @ts-ignore + it.each(fixtures)(`should return %s for %s in pnpm node_modules`, (expectedResult: boolean, pkgName: string) => { + expect(regex.test(`node_modules/.pnpm/${pkgName.replace(/\//g, '+')}/`)).toEqual(expectedResult); + expect(regex.test(`node_modules/.pnpm/registry.npmjs.org+${pkgName.replace(/\//g, '+')}/`)).toEqual( + expectedResult + ); }); }); - describe('should exclude the second package', () => { + describe('should exclude components and listed packages', () => { + let pattern; + let regex; + beforeAll(() => { + pattern = generateNodeModulesPattern({ packages: ['@myorg'], excludeComponents: true }); + regex = new RegExp(pattern); + }); it('should have yarn structure excluded', () => { expect(regex.test('node_modules/@myorg/something')).toBeFalsy(); }); - it('should have new pnpm structure with + excluded', () => { - expect(regex.test('node_modules/.pnpm/registry.npmjs.org+@myorg+something/')).toBeFalsy(); + }); + describe('should work with packages under the .pnpm directory', () => { + let pattern; + let regex; + beforeAll(() => { + pattern = generateNodeModulesPattern({ packages: ['@shohamgilad'], excludeComponents: false }); + regex = new RegExp(pattern); }); - it('should have new pnpm structure excluded', () => { - expect(regex.test('node_modules/.pnpm/@myorg+something/')).toBeFalsy(); + it('should exclude package under the .pnpm directory', () => { + expect( + regex.test( + 'node_modules/.pnpm/file+shohamgilad.test-new-env_ui_button@0.0.27_react@18.2.0/node_modules/@shohamgilad/test-new-env.ui.button/dist/index.js' + ) + ).toBeFalsy(); }); }); - describe('should exclude the third package', () => { - it('should have yarn structure excluded', () => { - expect(regex.test('node_modules/testing-library__dom/something')).toBeFalsy(); - }); - it('should have new pnpm structure with + excluded', () => { - expect(regex.test('node_modules/.pnpm/registry.npmjs.org+testing-library__dom/something')).toBeFalsy(); + describe('should work with components under the .pnpm directory', () => { + let pattern; + let regex; + beforeAll(() => { + pattern = generateNodeModulesPattern({ excludeComponents: true }); + regex = new RegExp(pattern); }); - it('should have new pnpm structure excluded', () => { - expect(regex.test('node_modules/.pnpm/testing-library__dom/something')).toBeFalsy(); + it('should exclude package under the .pnpm directory', () => { + expect( + regex.test( + 'node_modules/.pnpm/file+shohamgilad.test-new-env_ui_button@0.0.27_react@18.2.0/node_modules/@shohamgilad/test-new-env.ui.button/dist/index.js' + ) + ).toBeFalsy(); }); }); }); - describe('should not exclude the package when is not in the regex', () => { - let packagesToTransform; - let pattern; - let regex; - beforeAll(() => { - packagesToTransform = ['react']; - pattern = generateNodeModulesPattern({ packages: packagesToTransform }); - regex = new RegExp(pattern); + describe('format for webpack', () => { + describe('when packages provided is an empty array', () => { + it('should return an empty array', () => { + expect(generateNodeModulesPattern({ packages: [], target: PatternTarget.WEBPACK })).toEqual([]); + }); + }); + describe('when packages contains a single package', () => { + let patterns; + beforeAll(() => { + patterns = generateNodeModulesPattern({ + packages: ['@my-org/my-scope.components'], + target: PatternTarget.WEBPACK, + }); + }); + + it('should return an array with 2 patterns', () => { + expect((patterns || []).length).toEqual(2); + expect(patterns).toEqual([ + '^(.+?[\\/]node_modules[\\/](?!(@my-org[\\/]my-scope.components))(@.+?[\\/])?.+?)[\\/]', + '^(.+?[\\/]node_modules[\\/](?!(\\.pnpm[\\/](.*[+\\/])?@my-org\\+my-scope.components.*))(@.+?[\\/])?.+?)[\\/]', + ]); + }); }); - describe('should not exclude', () => { - it('with yarn structure', () => { - expect(regex.test('node_modules/not-excluded-package/some-path')).toBeTruthy(); + describe('when packages are provided', () => { + let patterns; + let regexps; + beforeAll(() => { + patterns = generateNodeModulesPattern({ + packages: ['@my-org/my-scope.components', '@other-org/my-scope.my-app'], + target: PatternTarget.WEBPACK, + }); + regexps = [...patterns.map((pattern) => new RegExp(pattern))]; + }); + + it('should exclude the packages from node modules', () => { + expect( + regexps.every((regexp) => regexp.test('node_modules/@my-org/my-scope.components/package.json')) + ).toBeFalsy(); + expect( + regexps.every((regexp) => regexp.test('node_modules/@my-org/my-scope.components/dist/index.ts')) + ).toBeFalsy(); }); - it('with old pnpm structure', () => { - expect(regex.test('node_modules/.pnpm/registry.npmjs.org/not-excluded-package/some-path')).toBeTruthy(); + + it('should exclude the packages from pnpm folder', () => { + expect(regexps.every((regexp) => regexp.test('node_modules/.pnpm/@my-org+my-scope.components'))).toBeFalsy(); + expect( + regexps.every((regexp) => regexp.test('node_modules/.pnpm/@my-org+my-scope.components/dist/index.ts')) + ).toBeFalsy(); }); - it("with new pnpm structure with '+'", () => { - expect(regex.test('node_modules/.pnpm/registry.npmjs.org+not-excluded-package/some-path')).toBeTruthy(); + + it('should exclude the packages from absolute paths', () => { + expect( + regexps.every((regexp) => + regexp.test('/Users/aUser/dev/bit-example/node_modules/@my-org/my-scope.components/package.json') + ) + ).toBeFalsy(); + expect( + regexps.every((regexp) => + regexp.test('/Users/aUser/dev/bit-example/node_modules/.pnpm/@my-org+my-scope.components/package.json') + ) + ).toBeFalsy(); }); - it('with old pnpm structure, different registry name', () => { + + it('should not exclude other packages', () => { expect( - regex.test('node_modules/.pnpm/registry.artifactory.something/not-excluded-package/some-path') + regexps.some((regexp) => + regexp.test('/Users/aUser/dev/bit-example/node_modules/@my-org/my-scope.apps/package.json') + ) ).toBeTruthy(); - }); - it("with new pnpm structure with '+'", () => { expect( - regex.test('node_modules/.pnpm/registry.artifactory.something+not-excluded-package/some-path') + regexps.some((regexp) => regexp.test('/Users/aUser/dev/bit-example/node_modules/@lodash/package.json')) + ).toBeTruthy(); + expect( + regexps.some((regexp) => regexp.test('/Users/aUser/dev/bit-example/node_modules/@react/package.json')) ).toBeTruthy(); }); - it('with new pnpm structure', () => { - expect(regex.test('node_modules/.pnpm/not-excluded-package/some-path')).toBeTruthy(); - }); - }); - }); - describe('should exclude components', () => { - let pattern; - let regex; - beforeAll(() => { - pattern = generateNodeModulesPattern({ excludeComponents: true }); - regex = new RegExp(pattern); - }); - const fixtures = [ - [true, 'not-a-component'], - [true, '@myorg/not-a-component'], - [true, 'scope.comp-name'], - [false, '@myorg/scope.comp-name'], - [false, '@myorg/scope.namespace.comp-name'], - [false, '@myorg/scope.ns1.ns2.comp-name'], - ]; - // @ts-ignore - it.each(fixtures)(`should return %s for %s in yarn node_modules`, (expectedResult: boolean, pkgName: string) => { - expect(regex.test(`node_modules/${pkgName}/`)).toEqual(expectedResult); - }); - // @ts-ignore - it.each(fixtures)(`should return %s for %s in pnpm node_modules`, (expectedResult: boolean, pkgName: string) => { - expect(regex.test(`node_modules/.pnpm/${pkgName.replace(/\//g, '+')}/`)).toEqual(expectedResult); - expect(regex.test(`node_modules/.pnpm/registry.npmjs.org+${pkgName.replace(/\//g, '+')}/`)).toEqual( - expectedResult - ); - }); - }); - describe('should exclude components and listed packages', () => { - let pattern; - let regex; - beforeAll(() => { - pattern = generateNodeModulesPattern({ packages: ['@myorg'], excludeComponents: true }); - regex = new RegExp(pattern); - }); - it('should have yarn structure excluded', () => { - expect(regex.test('node_modules/@myorg/something')).toBeFalsy(); - }); - }); - describe('should work with packages under the .pnpm directory', () => { - let pattern; - let regex; - beforeAll(() => { - pattern = generateNodeModulesPattern({ packages: ['@shohamgilad'], excludeComponents: false }); - regex = new RegExp(pattern); - }); - it('should exclude package under the .pnpm directory', () => { - expect( - regex.test( - 'node_modules/.pnpm/file+shohamgilad.test-new-env_ui_button@0.0.27_react@18.2.0/node_modules/@shohamgilad/test-new-env.ui.button/dist/index.js' - ) - ).toBeFalsy(); - }); - }); - describe('should work with components under the .pnpm directory', () => { - let pattern; - let regex; - beforeAll(() => { - pattern = generateNodeModulesPattern({ excludeComponents: true }); - regex = new RegExp(pattern); - }); - it('should exclude package under the .pnpm directory', () => { - expect( - regex.test( - 'node_modules/.pnpm/file+shohamgilad.test-new-env_ui_button@0.0.27_react@18.2.0/node_modules/@shohamgilad/test-new-env.ui.button/dist/index.js' - ) - ).toBeFalsy(); }); }); }); diff --git a/scopes/dependencies/modules/packages-excluder/generate-node-modules-pattern.ts b/scopes/dependencies/modules/packages-excluder/generate-node-modules-pattern.ts index c50320d6613c..12fa081317e8 100644 --- a/scopes/dependencies/modules/packages-excluder/generate-node-modules-pattern.ts +++ b/scopes/dependencies/modules/packages-excluder/generate-node-modules-pattern.ts @@ -1,4 +1,15 @@ -type generateNodeModulesPatternOptions = { +export enum PatternTarget { + /** + * Used in Jest `transformIgnorePatterns` options + */ + JEST = 'jest', + /** + * Used in Webpack `snapshot.managedPaths` options + */ + WEBPACK = 'webpack', +} + +type GenerateNodeModulesPatternOptions = { /** * An array of packages name to exclude in the regex. */ @@ -8,23 +19,36 @@ type generateNodeModulesPatternOptions = { * A component package looks like `@org/scope.namespace.component-name`. */ excludeComponents?: boolean; + /** + * The target for which patterns are generated. + */ + target?: T; +}; + +const patternTargetMap = { + [PatternTarget.JEST]: toJestPattern, + [PatternTarget.WEBPACK]: toWebpackPattern, }; +type PatternTargetMap = typeof patternTargetMap; +type PatternReturnType = ReturnType; + /** * A function that receives an array of packages names and returns a pattern (string) of a regex that matches any node_modules/package-name except the provided package-names. * @param {string[]} packages - array of packages. * @returns {string} node modules catched packages regex. */ -export function generateNodeModulesPattern({ - packages = [], - excludeComponents, -}: generateNodeModulesPatternOptions): string { - const negativeLookaheadPatterns = packages.reduce((acc: string[], curr) => { - const yarnPattern = curr; - const pnpmCurr = curr.replace(/\//g, '\\+'); - const pnpmPattern = `\\.pnpm/(.*[+/])?${pnpmCurr}.*`; +export function generateNodeModulesPattern( + options: GenerateNodeModulesPatternOptions = {} +): PatternReturnType { + const { packages = [], excludeComponents, target = PatternTarget.JEST } = options; + const negativeLookaheadPatterns = packages.reduce((acc: string[], packageName) => { + const yarnPattern = packageName.replace(/\//g, '[\\/]'); + const pnpmPackageName = packageName.replace(/\//g, '\\+'); + const pnpmPattern = `\\.pnpm[\\/](.*[+\\/])?${pnpmPackageName}.*`; return [...acc, yarnPattern, pnpmPattern]; }, []); + if (excludeComponents) { negativeLookaheadPatterns.push( '@[^/]+/([^/]+\\.)+[^/]+', @@ -32,6 +56,25 @@ export function generateNodeModulesPattern({ '\\.pnpm/.+/node_modules/@[^/]+/([^/]+\\.)+[^/]+' ); } - const transformIgnorePattern = `node_modules/(?!(${negativeLookaheadPatterns.join('|')})/)`; - return transformIgnorePattern; + + return patternTargetMap[target](negativeLookaheadPatterns) as PatternReturnType; +} + +function toJestPattern(patterns: string[]) { + return `node_modules/(?!(${patterns.join('|')})/)`; +} + +/** + * Webpack managed paths evaluate absolutes paths to `package.json` files. + * We need to generate a pattern that excludes the `package.json` files of the bit component packages. + * Example: + * - Component package: `@my-org/my-scope.components` + * - Webpack path: `/Users/aUser/dev/bit-example/node_modules/@my-org/my-scope.components/package.json` + * - RegExp to exclude this path from managed paths: `/^(.+?[\\/]node_modules[\\/](?!(@my-org[\\/]my-scope.components))(@.+?[\\/])?.+?)[\\/]/` + */ + +function toWebpackPattern(patterns: string[]) { + return patterns.map((pattern) => { + return `^(.+?[\\/]node_modules[\\/](?!(${pattern}))(@.+?[\\/])?.+?)[\\/]`; + }); } diff --git a/scopes/dependencies/modules/packages-excluder/index.ts b/scopes/dependencies/modules/packages-excluder/index.ts index ddc33c02d8fe..ca701cc33dde 100644 --- a/scopes/dependencies/modules/packages-excluder/index.ts +++ b/scopes/dependencies/modules/packages-excluder/index.ts @@ -1 +1 @@ -export { generateNodeModulesPattern } from './generate-node-modules-pattern'; +export { generateNodeModulesPattern, PatternTarget } from './generate-node-modules-pattern'; diff --git a/scopes/dependencies/modules/packages-excluder/packages-excluder.composition.tsx b/scopes/dependencies/modules/packages-excluder/packages-excluder.composition.tsx index 9d87d141ee1d..7cbfd2dad85e 100644 --- a/scopes/dependencies/modules/packages-excluder/packages-excluder.composition.tsx +++ b/scopes/dependencies/modules/packages-excluder/packages-excluder.composition.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { generateNodeModulesPattern } from './generate-node-modules-pattern'; +import { PatternTarget, generateNodeModulesPattern } from './generate-node-modules-pattern'; export function LiveExample() { const [text, setText] = useState('react,@myorg,some-lib'); @@ -18,14 +18,29 @@ export function RegexExample() { const [packagesToExclude, setPackagesToExclude] = useState('@myorg,react,some-lib'); const [packageToCheck, setPackageToCheck] = useState('@myorg'); const [excludeComponents, setExcludeComponents] = useState(false); - const [calculatedRegex, setCalculatedRegex] = useState(''); - const [regexResult, setRegexResult] = useState(true); + const [defaultCalculatedRegex, setDefaultCalculatedRegex] = useState(''); + const [webpackCalculatedRegexps, setWebpackCalculatedRegexps] = useState([]); + const [defaultRegexResult, setDefaultRegexResult] = useState(true); + const [webpackRegexResult, setWebpackRegexResult] = useState(true); useEffect(() => { - const pattern = generateNodeModulesPattern({ packages: packagesToExclude.split(','), excludeComponents }); - setCalculatedRegex(pattern); + const pattern = generateNodeModulesPattern({ packages: packagesToExclude.split(','), excludeComponents }) as string; + const webpackPatterns = generateNodeModulesPattern({ + packages: packagesToExclude.split(','), + excludeComponents, + target: PatternTarget.WEBPACK, + }); + setDefaultCalculatedRegex(pattern); + // @ts-ignore + setWebpackCalculatedRegexps(webpackPatterns); const regex = new RegExp(pattern); - setRegexResult(regex.test(`node_modules/${packageToCheck}/some-path`)); + const webpackRegexps = webpackPatterns.map((webpackPattern) => new RegExp(webpackPattern)); + setDefaultRegexResult(regex.test(`node_modules/${packageToCheck}/some-path`)); + setWebpackRegexResult( + webpackRegexps.every((webpackRegex) => + webpackRegex.test(`Users/aUser/workspace-a/node_modules/${packageToCheck}/package.json`) + ) + ); }, [packagesToExclude, packageToCheck, excludeComponents]); return ( @@ -38,19 +53,39 @@ export function RegexExample() { />
Exclude components:
setExcludeComponents(e.target.checked)} /> -
Write a package that you want to check with Regex test:
-
- - setPackageToCheck(e.target.value)} /> - -
-
- regex exclude {packageToCheck} excludeComponents: {excludeComponents.toString()} -
- regex: {calculatedRegex} -
- result: {regexResult.toString()} -
+ +
+

Default target (Jest)

+
Write a package that you want to check with Regex test:
+
+ + setPackageToCheck(e.target.value)} /> + +
+
+ regex exclude {packageToCheck} excludeComponents: {excludeComponents.toString()} +
+ regex: {defaultCalculatedRegex} +
+ result: {defaultRegexResult.toString()} +
+
+
+

Target Webpack

+
Write a package that you want to check with Regex test:
+
+ + setPackageToCheck(e.target.value)} /> + +
+
+ regex exclude {packageToCheck} excludeComponents: {excludeComponents.toString()} +
+ regex: {webpackCalculatedRegexps} +
+ result: {webpackRegexResult.toString()} +
+
); } diff --git a/scopes/dependencies/modules/packages-excluder/packages-excluder.docs.md b/scopes/dependencies/modules/packages-excluder/packages-excluder.docs.md index d03ab50f0e8c..fb0acea18aac 100644 --- a/scopes/dependencies/modules/packages-excluder/packages-excluder.docs.md +++ b/scopes/dependencies/modules/packages-excluder/packages-excluder.docs.md @@ -3,14 +3,20 @@ labels: ['typescript', 'utils', 'packages', 'node modules', 'node', 'regex', 'ex description: 'Create node modules regex with packages.' --- -import { generateNodeModulesPattern } from './generate-node-modules-pattern'; +import { generateNodeModulesPattern, PatternTarget } from './generate-node-modules-pattern'; A function that returns a pattern (string) of a regex that matches any `node_modules/package-name` except the ones that we want to exclude. +> **_NOTE:_** There are different cases of patterns that need to be generated. This depends on different capture groups that might need to be applied. At the moment matching patterns based on the target are supported. Check `target` option for details. + Options: - `packages` - **string[]** - optional. A list of package names and package scopes that we want to exclude. - `excludeComponents` - **boolean** - optional. If set to `true`, all component packages are excluded. +- `target` - **enum** - optional. Specifies the target for which patterns need to be generated for. Default target `PatternTarget.JEST`. + - Available pattern targets through `PatternTarget` enum: + - `JEST`: Used in Jest `transformIgnorePatterns` options + - `WEBPACK`: Used in Webpack `snapshot.managedPaths` options Basic example: @@ -21,6 +27,15 @@ Basic example: }; ``` +Basic example for `PatternTarget.WEBPACK` target: + +```js live +() => { + const packagesToTransform = ['@my-scope/my-button-component']; + return generateNodeModulesPattern({ packages: packagesToTransform, target: PatternTarget.WEBPACK }); +}; +``` + Regex exclude the package: ```js live @@ -32,6 +47,19 @@ Regex exclude the package: }; ``` +Regex exclude the package for `PatternTarget.WEBPACK` target: + +```js live +() => { + const packagesToTransform = ['@my-scope/my-button-component']; + const patterns = generateNodeModulesPattern({ packages: packagesToTransform, target: PatternTarget.WEBPACK }); + const regexps = patterns.map((pattern) => new RegExp(pattern)); + return regexps + .every((regex) => regex.test('Users/aUser/dev/node_modules/@my-scope/my-button-component/package.json')) + .toString(); +}; +``` + Regex not exclude the package: ```js live @@ -43,6 +71,19 @@ Regex not exclude the package: }; ``` +Regex not exclude the package for `PatternTarget.WEBPACK` target: + +```js live +() => { + const packagesToTransform = ['@my-scope/my-button-component']; + const patterns = generateNodeModulesPattern({ packages: packagesToTransform, target: PatternTarget.WEBPACK }); + const regexps = patterns.map((pattern) => new RegExp(pattern)); + return regexps + .some((regex) => regex.test('Users/aUser/dev/node_modules/not-excluded-package/package.json')) + .toString(); +}; +``` + Exclude components: ```js live diff --git a/scopes/harmony/application/build-application.task.ts b/scopes/harmony/application/build-application.task.ts index 271723de2941..a6959f716707 100644 --- a/scopes/harmony/application/build-application.task.ts +++ b/scopes/harmony/application/build-application.task.ts @@ -134,8 +134,9 @@ export class AppsBuildTask implements BuildTask { appResult.componentResult.warnings || [] ); // @ts-ignore - merged.componentResult._metadata.buildDeployContexts = // @ts-ignore - (merged.componentResult._metadata.buildDeployContexts || []) + merged.componentResult._metadata.buildDeployContexts = ( // @ts-ignore + merged.componentResult._metadata.buildDeployContexts || [] + ) // @ts-ignore .concat(appResult.componentResult._metadata || []); }); diff --git a/scopes/webpack/webpack/config/webpack.dev.config.ts b/scopes/webpack/webpack/config/webpack.dev.config.ts index 767db798df98..5bd350750bf7 100644 --- a/scopes/webpack/webpack/config/webpack.dev.config.ts +++ b/scopes/webpack/webpack/config/webpack.dev.config.ts @@ -25,6 +25,7 @@ export function configFactory( entryFiles: string[], publicRoot: string, publicPath: string, + componentPathsRegExps: RegExp[], pubsub: PubsubMain, title?: string, favicon?: string @@ -72,6 +73,7 @@ export function configFactory( stats: { errorDetails: true, + logging: 'error', }, devServer: { @@ -107,6 +109,7 @@ export function configFactory( client: { overlay: false, + logging: 'error', }, setupMiddlewares: (middlewares, devServer) => { @@ -160,6 +163,10 @@ export function configFactory( }), ], + snapshot: { + ...(componentPathsRegExps && componentPathsRegExps.length > 0 ? { managedPaths: componentPathsRegExps } : {}), + }, + watchOptions: { poll: true, }, diff --git a/scopes/webpack/webpack/webpack.main.runtime.ts b/scopes/webpack/webpack/webpack.main.runtime.ts index 686e0da86a6d..4e6519e24201 100644 --- a/scopes/webpack/webpack/webpack.main.runtime.ts +++ b/scopes/webpack/webpack/webpack.main.runtime.ts @@ -95,6 +95,7 @@ export class WebpackMain { context.id, context.rootPath, context.publicPath, + this.workspace.getComponentPathsRegExps(), context.title ) as any; const wdsPath = webpackDevServerModulePath || require.resolve('webpack-dev-server'); @@ -197,9 +198,19 @@ export class WebpackMain { devServerID: string, publicRoot: string, publicPath: string, + componentPathsRegExps: RegExp[], title?: string ) { - return devServerConfigFactory(devServerID, rootPath, entry, publicRoot, publicPath, this.pubsub, title); + return devServerConfigFactory( + devServerID, + rootPath, + entry, + publicRoot, + publicPath, + componentPathsRegExps, + this.pubsub, + title + ); } static slots = []; diff --git a/scopes/workspace/watcher/output-formatter.ts b/scopes/workspace/watcher/output-formatter.ts index 923bf1e48f5b..e535a5e01343 100644 --- a/scopes/workspace/watcher/output-formatter.ts +++ b/scopes/workspace/watcher/output-formatter.ts @@ -1,7 +1,7 @@ import { OnComponentEventResult } from '@teambit/workspace'; import chalk from 'chalk'; -const verboseComponentFilesArrayToString = (componentFiles) => { +const verboseComponentFilesArrayToString = (componentFiles = []) => { return componentFiles.reduce((outputString, filePath) => `${outputString} \t - ${filePath}\n`, ``); }; diff --git a/scopes/workspace/workspace/workspace.provider.ts b/scopes/workspace/workspace/workspace.provider.ts index 65e47b622bc9..10a1cf5cdffc 100644 --- a/scopes/workspace/workspace/workspace.provider.ts +++ b/scopes/workspace/workspace/workspace.provider.ts @@ -233,6 +233,9 @@ export default async function provideWorkspace( const workspaceSchema = getWorkspaceSchema(workspace, graphql); ui.registerUiRoot(new WorkspaceUIRoot(workspace, bundler)); + ui.registerPreStart(async () => { + return workspace.setComponentPathsRegExps(); + }); graphql.register(workspaceSchema); const capsuleCmd = getCapsulesCommands(isolator, scope, workspace); const commands: CommandList = [new EjectConfCmd(workspace), capsuleCmd, new UseCmd(workspace)]; diff --git a/scopes/workspace/workspace/workspace.ts b/scopes/workspace/workspace/workspace.ts index 5bbb565691ba..554a5e5fd538 100644 --- a/scopes/workspace/workspace/workspace.ts +++ b/scopes/workspace/workspace/workspace.ts @@ -6,6 +6,7 @@ import type { PubsubMain } from '@teambit/pubsub'; import { IssuesList } from '@teambit/component-issues'; import type { AspectLoaderMain, AspectDefinition } from '@teambit/aspect-loader'; import DependencyGraph from '@teambit/legacy/dist/scope/graph/scope-graph'; +import { generateNodeModulesPattern, PatternTarget } from '@teambit/dependencies.modules.packages-excluder'; import { AspectEntry, ComponentMain, @@ -151,6 +152,11 @@ export class Workspace implements ComponentFactory { private componentLoadedSelfAsAspects: InMemoryCache; // cache loaded components private aspectsMerger: AspectsMerger; private componentDefaultScopeFromComponentDirAndNameWithoutConfigFileMemoized; + /** + * Components paths are calculated from the component package names of the workspace + * They are used in webpack configuration to only track changes from these paths inside `node_modules` + */ + private componentPathsRegExps: RegExp[] = []; localAspects: string[] = []; constructor( /** @@ -235,6 +241,22 @@ export class Workspace implements ComponentFactory { } ); this.aspectsMerger = new AspectsMerger(this, this.harmony); + + this.registerOnComponentAdd(async () => { + await this.setComponentPathsRegExps(); + return { + results: this.componentPathsRegExps, + toString: () => this.componentPathsRegExps.join(), + }; + }); + + this.registerOnComponentRemove(async () => { + await this.setComponentPathsRegExps(); + return { + results: this.componentPathsRegExps, + toString: () => this.componentPathsRegExps.join(), + }; + }); } private validateConfig() { @@ -1797,6 +1819,20 @@ the following envs are used in this workspace: ${availableEnvs.join(', ')}`); await this.bitMap.write(); return { updated, alreadyUpToDate }; } + + getComponentPathsRegExps() { + return this.componentPathsRegExps; + } + + async setComponentPathsRegExps() { + const workspaceComponents = await this.list(); + const workspacePackageNames = workspaceComponents.map((c) => this.componentPackageName(c)); + const pathsExcluding = generateNodeModulesPattern({ + packages: workspacePackageNames, + target: PatternTarget.WEBPACK, + }); + this.componentPathsRegExps = [...pathsExcluding.map((stringPattern) => new RegExp(stringPattern))]; + } } /**