From 0e9f0ae6d5eda3c9bfe044ee51a58fb97facf8ce Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Thu, 21 Mar 2019 16:43:10 +0300 Subject: [PATCH] fix: watch on windows --- README.md | 168 ++++++++++++++++++++++++++++---------- src/postProcessPattern.js | 25 +++--- src/preProcessPattern.js | 32 ++++---- src/processPattern.js | 7 -- test/CopyPlugin.test.js | 157 ++++++++--------------------------- 5 files changed, 189 insertions(+), 200 deletions(-) diff --git a/README.md b/README.md index 101a7aa8..46d18e89 100644 --- a/README.md +++ b/README.md @@ -58,22 +58,34 @@ module.exports = { ### Patterns -| Name | Type | Default | Description | -| :-------------------------------: | :-------------------: | :---------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [`from`](#from) | `{String\|Object}` | `undefined` | Globs accept [minimatch options](https://github.com/isaacs/minimatch). See the [`node-glob` options](https://github.com/isaacs/node-glob#options) in addition to the ones below. | -| [`to`](#to) | `{String\|Object}` | `undefined` | Output root if `from` is file or dir, resolved glob path if `from` is glob. | -| [`toType`](#toType) | `{String}` | `undefined` | `[toType Options](#totype)`. | -| [`test`](#test) | `{RegExp}` | `undefined` | Pattern for extracting elements to be used in `to` templates. | -| [`force`](#force) | `{Boolean}` | `false` | Overwrites files already in `compilation.assets` (usually added by other plugins/loaders). | -| [`ignore`](#ignore) | `{Array}` | `[]` | Globs to ignore for this pattern. | -| [`flatten`](#flatten) | `{Boolean}` | `false` | Removes all directory references and only copies file names.⚠️ If files have the same name, the result is non-deterministic. | -| [`transform`](#transform) | `{Function\|Promise}` | `(content, path) => content` | Function or Promise that modifies file contents before copying. | -| [`transformPath`](#transformPath) | `{Function\|Promise}` | `(targetPath, sourcePath) => path` | Function or Promise that modifies file writing path. | -| [`cache`](#cache) | `{Boolean\|Object}` | `false` | Enable `transform` caching. You can use `{ cache: { key: 'my-cache-key' } }` to invalidate the cache. | -| [`context`](#context) | `{String}` | `options.context \|\| compiler.options.context` | A path that determines how to interpret the `from` path. | +| Name | Type | Default | Description | +| :-------------------------------: | :-------------------: | :---------------------------------------------: | :---------------------------------------------------------------------------------------------------- | +| [`from`](#from) | `{String\|Object}` | `undefined` | Glob or path from where we сopy files. | +| [`to`](#to) | `{String}` | `undefined` | Output path. | +| [`context`](#context) | `{String}` | `options.context \|\| compiler.options.context` | A path that determines how to interpret the `from` path. | +| [`toType`](#toType) | `{String}` | `undefined` | Determinate what is `to` option - directory, file or template. | +| [`test`](#test) | `{RegExp}` | `undefined` | Pattern for extracting elements to be used in `to` templates. | +| [`force`](#force) | `{Boolean}` | `false` | Overwrites files already in `compilation.assets` (usually added by other plugins/loaders). | +| [`ignore`](#ignore) | `{Array}` | `[]` | Globs to ignore files. | +| [`flatten`](#flatten) | `{Boolean}` | `false` | Removes all directory references and only copies file names. | +| [`transform`](#transform) | `{Function\|Promise}` | `undefined` | Allows to modify the file contents. | +| [`cache`](#cache) | `{Boolean\|Object}` | `false` | Enable `transform` caching. You can use `{ cache: { key: 'my-cache-key' } }` to invalidate the cache. | +| [`transformPath`](#transformPath) | `{Function\|Promise}` | `undefined` | Allows to modify the writing path. | #### `from` +Type: `String\|Object` +Default: `undefined` + +Glob or path from where we сopy files. +Globs accept [minimatch options](https://github.com/isaacs/minimatch). + +You can defined `from` as `Object` and use the [`node-glob` options](https://github.com/isaacs/node-glob#options). + +> ⚠️ Don't use directly `\\` in `from` (i.e `path\to\file.ext`) option because on UNIX the backslash is a valid character inside a path component, i.e., it's not a separator. +> On Windows, the forward slash and the backward slash are both separators. +> Instead please use `/` or `path` methods. + **webpack.config.js** ```js @@ -93,6 +105,15 @@ module.exports = { #### `to` +Type: `String` +Default: `undefined` + +Output path. + +> ⚠️ Don't use directly `\\` in `to` (i.e `path\to\dest`) option because on UNIX the backslash is a valid character inside a path component, i.e., it's not a separator. +> On Windows, the forward slash and the backward slash are both separators. +> Instead please use `/` or `path` methods. + **webpack.config.js** ```js @@ -106,8 +127,43 @@ module.exports = { }; ``` +#### `context` + +Type: `String` +Default: `options.context|compiler.options.context` + +A path that determines how to interpret the `from` path. + +> ⚠️ Don't use directly `\\` in `context` (i.e `path\to\context`) option because on UNIX the backslash is a valid character inside a path component, i.e., it's not a separator. +> On Windows, the forward slash and the backward slash are both separators. +> Instead please use `/` or `path` methods. + +**webpack.config.js** + +```js +module.exports = { + plugins: [ + new CopyPlugin([ + { + from: 'src/*.txt', + to: 'dest/', + context: 'app/', + }, + ]), + ], +}; +``` + #### `toType` +Type: `String` +Default: `undefined` + +Determinate what is `to` option - directory, file or template. +Sometimes it is hard to say what is `to`, example `path/to/dir-with.ext`. +If you want to copy files in directory you need use `dir` option. +We try to automatically determine the `type` so you most likely do not need this option. + | Name | Type | Default | Description | | :--------------: | :--------: | :---------: | :------------------------------------------------------------------------------------------------- | | **`'dir'`** | `{String}` | `undefined` | If `from` is directory, `to` has no extension or ends in `'/'` | @@ -170,6 +226,11 @@ module.exports = { #### `test` +Type: `RegExp` +Default: `undefined` + +Pattern for extracting elements to be used in `to` templates. + Defines a `{RegExp}` to match some parts of the file path. These capture groups can be reused in the name property using `[N]` placeholder. Note that `[0]` will be replaced by the entire path of the file, @@ -194,6 +255,11 @@ module.exports = { #### `force` +Type: `Boolean` +Default: `false` + +Overwrites files already in `compilation.assets` (usually added by other plugins/loaders). + **webpack.config.js** ```js @@ -212,6 +278,11 @@ module.exports = { #### `ignore` +Type: `Array` +Default: `[]` + +Globs to ignore files. + **webpack.config.js** ```js @@ -230,6 +301,13 @@ module.exports = { #### `flatten` +Type: `Boolean` +Default: `false` + +Removes all directory references and only copies file names. + +> ⚠️ If files have the same name, the result is non-deterministic. + **webpack.config.js** ```js @@ -248,6 +326,11 @@ module.exports = { #### `transform` +Type: `Function|Promise` +Default: `undefined` + +Allows to modify the file contents. + ##### `{Function}` **webpack.config.js** @@ -288,9 +371,13 @@ module.exports = { }; ``` -#### `transformPath` +#### `cache` -##### `{Function}` +Type: `Boolean|Object` +Default: `false` + +Enable/disable `transform` caching. You can use `{ cache: { key: 'my-cache-key' } }` to invalidate the cache. +Default path to cache directory: `node_modules/.cache/copy-webpack-plugin`. **webpack.config.js** @@ -301,36 +388,28 @@ module.exports = { { from: 'src/*.png', to: 'dest/', - transformPath(targetPath, absolutePath) { - return 'newPath'; + transform(content, path) { + return optimize(content); }, + cache: true, }, ]), ], }; ``` -##### `{Promise}` +#### `transformPath` -**webpack.config.js** +Type: `Function|Promise` +Default: `undefined` -```js -module.exports = { - plugins: [ - new CopyPlugin([ - { - from: 'src/*.png', - to: 'dest/', - transformPath(targePath, absolutePath) { - return Promise.resolve('newPath'); - }, - }, - ]), - ], -}; -``` +Allows to modify the writing path. -#### `cache` +> ⚠️ Don't return directly `\\` in `transformPath` (i.e `path\to\newFile`) option because on UNIX the backslash is a valid character inside a path component, i.e., it's not a separator. +> On Windows, the forward slash and the backward slash are both separators. +> Instead please use `/` or `path` methods. + +##### `{Function}` **webpack.config.js** @@ -341,17 +420,16 @@ module.exports = { { from: 'src/*.png', to: 'dest/', - transform(content, path) { - return optimize(content); + transformPath(targetPath, absolutePath) { + return 'newPath'; }, - cache: true, }, ]), ], }; ``` -#### `context` +##### `{Promise}` **webpack.config.js** @@ -360,9 +438,11 @@ module.exports = { plugins: [ new CopyPlugin([ { - from: 'src/*.txt', + from: 'src/*.png', to: 'dest/', - context: 'app/', + transformPath(targePath, absolutePath) { + return Promise.resolve('newPath'); + }, }, ]), ], @@ -404,6 +484,8 @@ module.exports = { #### `ignore` +Array of globs to ignore (applied to `from`). + **webpack.config.js** ```js @@ -414,6 +496,8 @@ module.exports = { #### `context` +A path that determines how to interpret the `from` path, shared for all patterns. + **webpack.config.js** ```js @@ -424,6 +508,8 @@ module.exports = { #### `copyUnmodified` +Copies files, regardless of modification when using watch or `webpack-dev-server`. All files are copied on first build, regardless of this option. + > ℹ️ By default, we only copy **modified** files during a `webpack --watch` or `webpack-dev-server` build. Setting this option to `true` will copy all files. **webpack.config.js** diff --git a/src/postProcessPattern.js b/src/postProcessPattern.js index 3a57d557..cd4bdbe2 100644 --- a/src/postProcessPattern.js +++ b/src/postProcessPattern.js @@ -6,7 +6,6 @@ import loaderUtils from 'loader-utils'; import cacache from 'cacache'; import serialize from 'serialize-javascript'; import findCacheDir from 'find-cache-dir'; -import normalizePath from 'normalize-path'; import { name, version } from '../package.json'; @@ -114,18 +113,18 @@ export default function postProcessPattern(globalRef, pattern, file) { file.webpackTo = file.webpackTo.replace(/\.?\[ext\]/g, ''); } - // Developers can use invalid slashes in regex we should fix it - file.webpackTo = normalizePath( - loaderUtils.interpolateName( - { resourcePath: file.absoluteFrom }, - file.webpackTo, - { - content, - regExp: file.webpackToRegExp, - context: pattern.context, - } - ) + file.webpackTo = loaderUtils.interpolateName( + { resourcePath: file.absoluteFrom }, + file.webpackTo, + { + content, + regExp: file.webpackToRegExp, + context: pattern.context, + } ); + + // Bug in `loader-utils`, package convert `\\` to `/`, need fix in loader-utils + file.webpackTo = path.normalize(file.webpackTo); } return content; @@ -141,7 +140,7 @@ export default function postProcessPattern(globalRef, pattern, file) { ) .then((newPath) => { // Developers can use invalid slashes we should fix it - file.webpackTo = normalizePath(newPath); + file.webpackTo = path.normalize(newPath); }) .then(() => content); } diff --git a/src/preProcessPattern.js b/src/preProcessPattern.js index 95737a3f..4e7d6de2 100644 --- a/src/preProcessPattern.js +++ b/src/preProcessPattern.js @@ -2,7 +2,6 @@ import path from 'path'; import isGlob from 'is-glob'; import globParent from 'glob-parent'; -import normalizePath from 'normalize-path'; import normalize from './utils/normalize'; import isObject from './utils/isObject'; @@ -45,7 +44,16 @@ export default function preProcessPattern(globalRef, pattern) { pattern.context = path.join(context, pattern.context); } - pattern.context = normalizePath(pattern.context); + const isFromGlobPatten = isObject(pattern.from) && pattern.from.glob; + // Todo remove this in next major + const isToDirectory = + path.extname(pattern.to) === '' || pattern.to.slice(-1) === path.sep; + + // Normalize paths + pattern.from = isFromGlobPatten ? pattern.from : path.normalize(pattern.from); + pattern.context = path.normalize(pattern.context); + pattern.to = path.normalize(pattern.to); + pattern.ignore = globalRef.ignore.concat(pattern.ignore || []); logger.debug(`processing from: '${pattern.from}' to: '${pattern.to}'`); @@ -57,7 +65,7 @@ export default function preProcessPattern(globalRef, pattern) { case isTemplateLike.test(pattern.to): pattern.toType = 'template'; break; - case path.extname(pattern.to) === '' || pattern.to.slice(-1) === '/': + case isToDirectory: pattern.toType = 'dir'; break; default: @@ -65,7 +73,7 @@ export default function preProcessPattern(globalRef, pattern) { } // If we know it's a glob, then bail early - if (isObject(pattern.from) && pattern.from.glob) { + if (isFromGlobPatten) { logger.debug(`determined '${pattern.absoluteFrom}' is a glob`); pattern.fromType = 'glob'; @@ -75,9 +83,7 @@ export default function preProcessPattern(globalRef, pattern) { pattern.glob = normalize(pattern.context, pattern.from.glob); pattern.globOptions = globOptions; - pattern.absoluteFrom = normalizePath( - path.resolve(pattern.context, pattern.from.glob) - ); + pattern.absoluteFrom = path.resolve(pattern.context, pattern.from.glob); return Promise.resolve(pattern); } @@ -88,9 +94,6 @@ export default function preProcessPattern(globalRef, pattern) { pattern.absoluteFrom = path.resolve(pattern.context, pattern.from); } - // Normalize path when path separators are mixed (like `C:\\directory/nested-directory/`) - pattern.absoluteFrom = normalizePath(pattern.absoluteFrom); - logger.debug( `determined '${pattern.from}' to be read from '${pattern.absoluteFrom}'` ); @@ -105,7 +108,8 @@ export default function preProcessPattern(globalRef, pattern) { // We need to add context directory as dependencies to avoid problems when new files added in directories // when we already in watch mode and this directories are not in context dependencies - contextDependencies.add(globParent(pattern.absoluteFrom)); + // `glob-parent` always return `/` we need normalize path + contextDependencies.add(path.normalize(globParent(pattern.absoluteFrom))); } else { const newWarning = new Error( `unable to locate '${pattern.from}' at '${pattern.absoluteFrom}'` @@ -147,9 +151,7 @@ export default function preProcessPattern(globalRef, pattern) { pattern.fromType = 'dir'; pattern.context = pattern.absoluteFrom; pattern.glob = normalize(pattern.absoluteFrom, '**/*'); - pattern.absoluteFrom = normalizePath( - path.join(pattern.absoluteFrom, '**/*') - ); + pattern.absoluteFrom = path.join(pattern.absoluteFrom, '**/*'); pattern.globOptions = { dot: true, }; @@ -159,7 +161,7 @@ export default function preProcessPattern(globalRef, pattern) { fileDependencies.add(pattern.absoluteFrom); pattern.fromType = 'file'; - pattern.context = normalizePath(path.dirname(pattern.absoluteFrom)); + pattern.context = path.dirname(pattern.absoluteFrom); pattern.glob = normalize(pattern.absoluteFrom); pattern.globOptions = { dot: true, diff --git a/src/processPattern.js b/src/processPattern.js index 2dfd1cd0..1469eab1 100644 --- a/src/processPattern.js +++ b/src/processPattern.js @@ -3,7 +3,6 @@ import path from 'path'; import globby from 'globby'; import pLimit from 'p-limit'; import minimatch from 'minimatch'; -import normalizePath from 'normalize-path'; import isObject from './utils/isObject'; @@ -42,9 +41,6 @@ export default function processPattern(globalRef, pattern) { file.relativeFrom = path.basename(file.relativeFrom); } - // Ensure forward slashes - file.relativeFrom = normalizePath(file.relativeFrom); - logger.debug(`found ${from}`); // Check the ignore list @@ -113,9 +109,6 @@ export default function processPattern(globalRef, pattern) { file.webpackTo = path.relative(output, file.webpackTo); } - // Ensure forward slashes - file.webpackTo = normalizePath(file.webpackTo); - logger.info( `determined that '${from}' should write to '${file.webpackTo}'` ); diff --git a/test/CopyPlugin.test.js b/test/CopyPlugin.test.js index 2fe21de9..54039ef0 100644 --- a/test/CopyPlugin.test.js +++ b/test/CopyPlugin.test.js @@ -10,7 +10,6 @@ import findCacheDir from 'find-cache-dir'; import cacache from 'cacache'; import isGzip from 'is-gzip'; import mkdirp from 'mkdirp'; -import normalizePath from 'normalize-path'; import CopyPlugin from '../src/index'; @@ -160,7 +159,10 @@ describe('apply function', () => { run(opts).then((compilation) => { if (opts.expectedAssetKeys && opts.expectedAssetKeys.length > 0) { expect(Object.keys(compilation.assets).sort()).toEqual( - opts.expectedAssetKeys.sort().map(removeIllegalCharacterForWindows) + opts.expectedAssetKeys + .sort() + .map(removeIllegalCharacterForWindows) + .map((item) => item.replace(/\//g, path.sep)) ); } else { expect(compilation.assets).toEqual({}); @@ -169,16 +171,18 @@ describe('apply function', () => { if (opts.expectedAssetContent) { // eslint-disable-next-line guard-for-in for (const key in opts.expectedAssetContent) { - expect(compilation.assets[key]).toBeDefined(); + const assetName = key.replace(/(\/|\\)/g, path.sep); - if (compilation.assets[key]) { + expect(compilation.assets[assetName]).toBeDefined(); + + if (compilation.assets[assetName]) { let expectedContent = opts.expectedAssetContent[key]; if (!Buffer.isBuffer(expectedContent)) { expectedContent = Buffer.from(expectedContent); } - let compiledContent = compilation.assets[key].source(); + let compiledContent = compilation.assets[assetName].source(); if (!Buffer.isBuffer(compiledContent)) { compiledContent = Buffer.from(compiledContent); @@ -237,13 +241,17 @@ describe('apply function', () => { .then(() => { if (opts.expectedAssetKeys && opts.expectedAssetKeys.length > 0) { expect(Object.keys(compilation.assets).sort()).toEqual( - opts.expectedAssetKeys.sort() + opts.expectedAssetKeys + .sort() + .map(removeIllegalCharacterForWindows) + .map((item) => item.replace(/\//g, path.sep)) ); } else { expect(compilation.assets).toEqual({}); } }) .then(() => { + // Todo finally and check file exists fs.unlinkSync(opts.newFileLoc1); fs.unlinkSync(opts.newFileLoc2); }); @@ -485,7 +493,10 @@ describe('apply function', () => { transformPath(targetPath, absoluteFrom) { expect(absoluteFrom.includes(HELPER_DIR)).toBe(true); - return targetPath.replace('nested/', 'transformed/'); + return targetPath.replace( + `nested${path.sep}`, + `transformed${path.sep}` + ); }, }, ], @@ -687,9 +698,9 @@ describe('apply function', () => { .then((compilation) => { expect( Array.from(compilation.contextDependencies) - .map((contextDependency) => normalizePath(contextDependency)) + .map((contextDependency) => contextDependency) .sort() - ).toEqual([normalizePath(path.join(HELPER_DIR, 'directory'))].sort()); + ).toEqual([path.join(HELPER_DIR, 'directory')].sort()); }) .then(done) .catch(done); @@ -797,9 +808,9 @@ describe('apply function', () => { expectedAssetKeys: [], expectedWarnings: [ new Error( - `unable to locate 'nonexistent.txt' at '${normalizePath( - HELPER_DIR - )}/nonexistent.txt'` + `unable to locate 'nonexistent.txt' at '${HELPER_DIR}${ + path.sep + }nonexistent.txt'` ), ], patterns: [ @@ -818,9 +829,9 @@ describe('apply function', () => { expectedAssetKeys: [], expectedWarnings: [ new Error( - `unable to locate 'nonexistent.txt' at '${normalizePath( - HELPER_DIR - )}/nonexistent.txt'` + `unable to locate 'nonexistent.txt' at '${HELPER_DIR}${ + path.sep + }nonexistent.txt'` ), ], patterns: [ @@ -1057,13 +1068,13 @@ describe('apply function', () => { .catch(done); }); - it('can move a file to a new directory with an extension and forward slash', (done) => { + it('can move a file to a new directory with an extension and path separator at end', (done) => { runEmit({ expectedAssetKeys: ['newdirectory.ext/file.txt'], patterns: [ { from: 'file.txt', - to: 'newdirectory.ext/', + to: `newdirectory.ext${path.sep}`, }, ], }) @@ -1236,7 +1247,7 @@ describe('apply function', () => { ], }) .then((compilation) => { - const absFrom = normalizePath(path.join(HELPER_DIR, 'file.txt')); + const absFrom = path.join(HELPER_DIR, 'file.txt'); expect(Array.from(compilation.fileDependencies).sort()).toEqual( [absFrom].sort() @@ -1478,9 +1489,9 @@ describe('apply function', () => { expectedAssetKeys: [], expectedWarnings: [ new Error( - `unable to locate 'nonexistent' at '${normalizePath( - HELPER_DIR - )}/nonexistent'` + `unable to locate 'nonexistent' at '${HELPER_DIR}${ + path.sep + }nonexistent'` ), ], patterns: [ @@ -1680,7 +1691,7 @@ describe('apply function', () => { ], }) .then((compilation) => { - const absFrom = normalizePath(path.resolve(HELPER_DIR, 'directory')); + const absFrom = path.resolve(HELPER_DIR, 'directory'); expect(Array.from(compilation.contextDependencies).sort()).toEqual( [absFrom].sort() ); @@ -1822,71 +1833,6 @@ describe('apply function', () => { .catch(done); }); - it('can normalize mixed path segment separation with glob in from (simple)', (done) => { - runEmit({ - expectedAssetKeys: ['directory/nested/nestedfile.txt'], - patterns: [ - { - from: 'directory/nested\\*', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can normalize backslash path with file in from', (done) => { - runEmit({ - expectedAssetKeys: ['nestedfile.txt'], - patterns: [ - { - from: 'directory\\nested\\nestedfile.txt', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can normalize backslash path with file in from (mixed path segment separation)', (done) => { - runEmit({ - expectedAssetKeys: ['nestedfile.txt'], - patterns: [ - { - from: 'directory\\nested/nestedfile.txt', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can normalize backslash path with directory in from', (done) => { - runEmit({ - expectedAssetKeys: ['deepnested.txt'], - patterns: [ - { - from: 'directory\\nested\\deep-nested', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can normalize backslash path with directory in from (mixed path segment separation)', (done) => { - runEmit({ - expectedAssetKeys: ['deepnested.txt'], - patterns: [ - { - from: 'directory\\nested\\deep-nested', - }, - ], - }) - .then(done) - .catch(done); - }); - it('can exclude path', (done) => { runEmit({ expectedAssetKeys: [ @@ -1928,43 +1874,6 @@ describe('apply function', () => { .then(done) .catch(done); }); - - it('can normalize backslash in context', (done) => { - runEmit({ - expectedAssetKeys: [ - 'newdirectory/deep-nested/deepnested.txt', - 'newdirectory/nestedfile.txt', - ], - options: { - context: path.resolve(HELPER_DIR, 'directory\\'), - }, - patterns: [ - { - from: 'nested', - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can normalize backslash in context (2)', (done) => { - runEmit({ - expectedAssetKeys: ['newdirectory/deepnested.txt'], - options: { - context: path.resolve(HELPER_DIR, 'directory\\nested'), - }, - patterns: [ - { - from: 'deep-nested', - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); - }); }); describe('modified files', () => {