├── .babelrc ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── appveyor.yml ├── package-lock.json ├── package.json ├── scripts └── createSpecialDirectory.js ├── src ├── copyWebpackPlugin.js ├── extractComponent.js ├── fetchModules.js ├── index.js ├── preProcessPattern.js ├── processPattern.js ├── utils │ ├── escape.js │ ├── isObject.js │ └── promisify.js └── writeFile.js ├── tests ├── components.test.js ├── helpers │ ├── [!] │ │ └── hello.txt │ ├── binextension.bin │ ├── directory │ │ ├── .dottedfile │ │ ├── directoryfile.txt │ │ └── nested │ │ │ └── nestedfile.txt │ ├── file.txt │ ├── file.txt.gz │ └── noextension ├── index.js ├── utils │ └── removeIllegalCharacterForWindows.js └── weappHelpers │ ├── components │ ├── base │ │ ├── Dep │ │ │ └── index.js │ │ └── Page.js │ ├── button │ │ ├── index.js │ │ ├── index.json │ │ └── index.wxml │ ├── comp1 │ │ ├── index.js │ │ ├── index.json │ │ └── index.wxml │ └── panel │ │ ├── index.js │ │ ├── index.json │ │ └── index.wxml │ ├── entries.js │ └── pages │ ├── native │ ├── index.js │ ├── index.json │ └── index.wxml │ ├── normal │ └── index.json │ ├── notexist │ └── index.json │ └── self │ └── index.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true, 5 | "mocha": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "indent": [ 13 | "error", 14 | 4 15 | ], 16 | "linebreak-style": [ 17 | "error", 18 | "unix" 19 | ], 20 | "quotes": [ 21 | "error", 22 | "single" 23 | ], 24 | "semi": [ 25 | "error", 26 | "always" 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | package-lock.json -diff 4 | bin/* eol=lf 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | *.log 5 | .* 6 | !.gitignore 7 | !.npmignore 8 | !.babelrc 9 | !.travis.yml 10 | !.eslintrc.json 11 | !tests/helpers/directory/.dottedfile 12 | compiled_tests 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - stable 5 | - lts/* 6 | - 6 7 | - 4 8 | 9 | notifications: 10 | email: false -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | ## [4.7.3](https://github.com/JJJYY/import-weapp-component/compare/v4.7.2...v4.7.3) (2018-11-04) 7 | 8 | 9 | ### Features 10 | 11 | * support copy dependencies file ([f557296](https://github.com/JJJYY/import-weapp-component/commit/f557296)) 12 | 13 | 14 | 15 | 16 | ## [4.7.2](https://github.com/JJJYY/import-weapp-component/compare/v4.7.0...v4.7.2) (2018-09-16) 17 | 18 | 19 | ### Features 20 | 21 | * 支持 mpvue-entry([#4](https://github.com/JJJYY/import-weapp-component/issues/4)) ([5b0f619](https://github.com/JJJYY/import-weapp-component/commit/5b0f619)) 22 | 23 | 24 | 25 | 26 | # [4.7.0](https://github.com/JJJYY/import-weapp-component/compare/v4.5.5...v4.7.0) (2018-08-26) 27 | 28 | 29 | ### Features 30 | 31 | * support mpvue 1.0.12 and native page import ([312aab0](https://github.com/JJJYY/import-weapp-component/commit/312aab0)) 32 | 33 | 34 | 35 | 36 | # [4.6.0](https://github.com/JJJYY/import-weapp-component/compare/v4.5.5...v4.6.0) (2018-08-26) 37 | 38 | 39 | ### Features 40 | 41 | * support mpvue 1.0.12 and native page import ([312aab0](https://github.com/JJJYY/import-weapp-component/commit/312aab0)) 42 | 43 | 44 | 45 | 46 | ## [4.5.5](https://github.com/JJJYY/import-weapp-component/compare/v4.5.1...v4.5.5) (2018-07-06) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * [#2](https://github.com/JJJYY/import-weapp-component/issues/2) native dependencies not bundle ([84a67e1](https://github.com/JJJYY/import-weapp-component/commit/84a67e1)) 52 | * [#3](https://github.com/JJJYY/import-weapp-component/issues/3) relative path import ([93aa211](https://github.com/JJJYY/import-weapp-component/commit/93aa211)) 53 | * add import component entry ([499c755](https://github.com/JJJYY/import-weapp-component/commit/499c755)) 54 | * allow square brackets in path ([#264](https://github.com/JJJYY/import-weapp-component/issues/264)) ([3ef5b6c](https://github.com/JJJYY/import-weapp-component/commit/3ef5b6c)), closes [#231](https://github.com/JJJYY/import-weapp-component/issues/231) 55 | * fix copy webpack test ([331c667](https://github.com/JJJYY/import-weapp-component/commit/331c667)) 56 | * keep faces together with weapp import component ([5ab2bd7](https://github.com/JJJYY/import-weapp-component/commit/5ab2bd7)) 57 | * windows get file dir from path ([deaa923](https://github.com/JJJYY/import-weapp-component/commit/deaa923)) 58 | 59 | 60 | ### Features 61 | 62 | * add auto copy component to output ([f3e48fe](https://github.com/JJJYY/import-weapp-component/commit/f3e48fe)) 63 | * throw error when path not exist ([94aa056](https://github.com/JJJYY/import-weapp-component/commit/94aa056)) 64 | 65 | 66 | 67 | 68 | ## [4.5.1](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.5.0...v4.5.1) (2018-03-09) 69 | 70 | 71 | ### Bug Fixes 72 | 73 | * **package:** update `cacache` v10.0.1...10.0.4 (`dependencies`) ([#238](https://github.com/webpack-contrib/copy-webpack-plugin/issues/238)) ([0b288f9](https://github.com/webpack-contrib/copy-webpack-plugin/commit/0b288f9)) 74 | 75 | 76 | ### Performance Improvements 77 | 78 | * **index:** switch to `md4` for content hashing ([#239](https://github.com/webpack-contrib/copy-webpack-plugin/issues/239)) ([2be8191](https://github.com/webpack-contrib/copy-webpack-plugin/commit/2be8191)) 79 | 80 | 81 | 82 | 83 | # [4.5.0](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.4.3...v4.5.0) (2018-03-02) 84 | 85 | 86 | ### Features 87 | 88 | * **processPattern:** add support for `{RegExp)` matches (`pattern.test`) ([#235](https://github.com/webpack-contrib/copy-webpack-plugin/issues/235)) ([1861730](https://github.com/webpack-contrib/copy-webpack-plugin/commit/1861730)) 89 | 90 | 91 | 92 | 93 | ## [4.4.3](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.4.2...v4.4.3) (2018-03-01) 94 | 95 | 96 | ### Bug Fixes 97 | 98 | * **index:** `tapable` deprecation warnings (`webpack >= v4.0.0`) ([#234](https://github.com/webpack-contrib/copy-webpack-plugin/issues/234)) ([445d548](https://github.com/webpack-contrib/copy-webpack-plugin/commit/445d548)) 99 | 100 | 101 | 102 | 103 | ## [4.4.2](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.4.1...v4.4.2) (2018-02-23) 104 | 105 | 106 | ### Bug Fixes 107 | 108 | * **src/:** don't escape non-glob patterns ([#230](https://github.com/webpack-contrib/copy-webpack-plugin/issues/230)) ([0eb2cd5](https://github.com/webpack-contrib/copy-webpack-plugin/commit/0eb2cd5)) 109 | 110 | 111 | 112 | 113 | ## [4.4.1](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.4.0...v4.4.1) (2018-02-08) 114 | 115 | 116 | ### Bug Fixes 117 | 118 | * replace `pify` with simpler promise helpers ([#221](https://github.com/webpack-contrib/copy-webpack-plugin/issues/221)) ([dadac24](https://github.com/webpack-contrib/copy-webpack-plugin/commit/dadac24)) 119 | 120 | 121 | 122 | 123 | # [4.4.0](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.3.1...v4.4.0) (2018-02-08) 124 | 125 | 126 | ### Bug Fixes 127 | 128 | * **package:** add `prepare` script ([9bf0d99](https://github.com/webpack-contrib/copy-webpack-plugin/commit/9bf0d99)) 129 | * **preProcessPatterns:** support glob context paths with special characters ([#208](https://github.com/webpack-contrib/copy-webpack-plugin/issues/208)) ([ea0c05f](https://github.com/webpack-contrib/copy-webpack-plugin/commit/ea0c05f)) 130 | * support `webpack >= v4.0.0` ([6a16b3c](https://github.com/webpack-contrib/copy-webpack-plugin/commit/6a16b3c)) 131 | 132 | 133 | ### Features 134 | 135 | * use `compiler.inputFileSystem` instead `fs` ([#205](https://github.com/webpack-contrib/copy-webpack-plugin/issues/205)) ([158f821](https://github.com/webpack-contrib/copy-webpack-plugin/commit/158f821)) 136 | 137 | 138 | 139 | 140 | ## [4.3.1](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.3.0...v4.3.1) (2017-12-22) 141 | 142 | 143 | ### Bug Fixes 144 | 145 | * `cache` behaviour ([#196](https://github.com/webpack-contrib/copy-webpack-plugin/issues/196)) ([6beb89e](https://github.com/webpack-contrib/copy-webpack-plugin/commit/6beb89e)) 146 | * `cache` option behaviour ([3b088d0](https://github.com/webpack-contrib/copy-webpack-plugin/commit/3b088d0)) 147 | 148 | 149 | 150 | 151 | # [4.3.0](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.2.4...v4.3.0) (2017-12-14) 152 | 153 | 154 | ### Features 155 | 156 | * add option to cache `pattern.transform` (`pattern.cache`) ([#176](https://github.com/webpack-contrib/copy-webpack-plugin/issues/176)) ([20c143b](https://github.com/webpack-contrib/copy-webpack-plugin/commit/20c143b)) 157 | * option for caching `transform` function ([48c19ff](https://github.com/webpack-contrib/copy-webpack-plugin/commit/48c19ff)) 158 | 159 | 160 | 161 | 162 | ## [4.2.4](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.2.3...v4.2.4) (2017-12-14) 163 | 164 | 165 | ### Refactoring 166 | 167 | * refactor: use native `{Promise}` instead of `bluebird` ([#178](https://github.com/webpack-contrib/copy-webpack-plugin/issues/178)) ([a508f14](https://github.com/webpack-contrib/copy-webpack-plugin/commit/a508f14)) 168 | 169 | 170 | 171 | 172 | ## [4.2.3](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.2.2...v4.2.3) (2017-11-23) 173 | 174 | 175 | 176 | 177 | ## [4.2.2](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.2.0...v4.2.2) (2017-11-23) 178 | 179 | 180 | ### Bug Fixes 181 | 182 | * copying same file to multiple targets ([#165](https://github.com/webpack-contrib/copy-webpack-plugin/issues/165)) ([43a9870](https://github.com/webpack-contrib/copy-webpack-plugin/commit/43a9870)) 183 | 184 | 185 | 186 | 187 | # [4.2.0](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.1.1...v4.2.0) (2017-10-19) 188 | 189 | 190 | ### Features 191 | 192 | * add `context` option (`options.context`) ([#149](https://github.com/webpack-contrib/copy-webpack-plugin/issues/149)) ([10cd1a2](https://github.com/webpack-contrib/copy-webpack-plugin/commit/10cd1a2)) 193 | * allow async transforms ([#111](https://github.com/webpack-contrib/copy-webpack-plugin/issues/111)) ([8794e5f](https://github.com/webpack-contrib/copy-webpack-plugin/commit/8794e5f)) 194 | * Plugin context option ([5c54e92](https://github.com/webpack-contrib/copy-webpack-plugin/commit/5c54e92)), closes [#148](https://github.com/webpack-contrib/copy-webpack-plugin/issues/148) 195 | * support `{String}` patterns ([#155](https://github.com/webpack-contrib/copy-webpack-plugin/issues/155)) ([b6c2e66](https://github.com/webpack-contrib/copy-webpack-plugin/commit/b6c2e66)) 196 | * Support simple string patterns ([056a60b](https://github.com/webpack-contrib/copy-webpack-plugin/commit/056a60b)), closes [#150](https://github.com/webpack-contrib/copy-webpack-plugin/issues/150) 197 | 198 | 199 | 200 | 201 | ## [4.1.1](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.1.0...v4.1.1) (2017-10-05) 202 | 203 | 204 | ### Chore 205 | 206 | * Update dependencies for NSP security advisory ([#151](https://github.com/webpack-contrib/copy-webpack-plugin/issues/151)) ([6d4346e](https://github.com/webpack-contrib/copy-webpack-plugin/commit/6d4346e)) 207 | 208 | - Reference issue: https://nodesecurity.io/advisories/minimatch_regular-expression-denial-of-service 209 | 210 | 211 | 212 | 213 | # [4.1.0](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v2.1.3...v4.1.0) (2017-09-29) 214 | 215 | 216 | ### Bug Fixes 217 | 218 | * Changed default ignore glob to ignore dot files ([#80](https://github.com/webpack-contrib/copy-webpack-plugin/issues/80)) ([08b69a4](https://github.com/webpack-contrib/copy-webpack-plugin/commit/08b69a4)) 219 | * Fixed glob as object ([1b2c21a](https://github.com/webpack-contrib/copy-webpack-plugin/commit/1b2c21a)) 220 | * Improved Windows compatibility ([#85](https://github.com/webpack-contrib/copy-webpack-plugin/issues/85)) ([ad62899](https://github.com/webpack-contrib/copy-webpack-plugin/commit/ad62899)) 221 | * Memory leak in watch mode and use Set for performance ([#130](https://github.com/webpack-contrib/copy-webpack-plugin/issues/130)) ([de46fde](https://github.com/webpack-contrib/copy-webpack-plugin/commit/de46fde)) 222 | * subdirectory errors in blob patterns ([c2720d0](https://github.com/webpack-contrib/copy-webpack-plugin/commit/c2720d0)) 223 | 224 | 225 | ### Features 226 | 227 | * Added non-wildcard glob support ([405d1ec](https://github.com/webpack-contrib/copy-webpack-plugin/commit/405d1ec)) 228 | * Added transform method to patterns ([#77](https://github.com/webpack-contrib/copy-webpack-plugin/issues/77)) ([6371eb1](https://github.com/webpack-contrib/copy-webpack-plugin/commit/6371eb1)) 229 | 230 | 231 | 232 | 233 | ## [4.0.1](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v2.1.3...v4.0.1) (2017-09-29) 234 | 235 | 236 | ### Bug Fixes 237 | 238 | * Fixed glob as object ([1b2c21a](https://github.com/webpack-contrib/copy-webpack-plugin/commit/1b2c21a)) 239 | * Improved Windows compatibility ([#85](https://github.com/webpack-contrib/copy-webpack-plugin/issues/85)) ([ad62899](https://github.com/webpack-contrib/copy-webpack-plugin/commit/ad62899)) 240 | * subdirectory errors in blob patterns ([c2720d0](https://github.com/webpack-contrib/copy-webpack-plugin/commit/c2720d0)) 241 | 242 | 243 | ### Features 244 | 245 | * Added non-wildcard glob support ([405d1ec](https://github.com/webpack-contrib/copy-webpack-plugin/commit/405d1ec)) 246 | * Added transform method to patterns ([#77](https://github.com/webpack-contrib/copy-webpack-plugin/issues/77)) ([6371eb1](https://github.com/webpack-contrib/copy-webpack-plugin/commit/6371eb1)) 247 | 248 | 249 | 250 | 251 | ## [4.0.0](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v4.0.0...v3.0.1) (2016-10-23) 252 | 253 | 254 | ### Bug Fixes 255 | 256 | * Changed default ignore glob to ignore dot files ([#80](https://github.com/webpack-contrib/copy-webpack-plugin/issues/80)) ([08b69a4](https://github.com/webpack-contrib/copy-webpack-plugin/commit/08b69a4)) 257 | 258 | ### Features 259 | 260 | * Added transform method to patterns ([6371eb1](https://github.com/webpack-contrib/copy-webpack-plugin/commit/6371eb1)) 261 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Forked from [webpack-contrib/copy-webpack-plugin](https://github.com/webpack-contrib/copy-webpack-plugin) 2 | 3 | Author: Len Boyette 4 | 5 | License: MIT 6 | 7 | 简介:根据`json`文件`usingComponents`自动引入组件,兼容[mpvue-entry](https://github.com/F-loat/mpvue-entry) 8 | 9 | 支持最新版本`mpvue`, 请按照如下用法进行配置 10 | 11 | ----------- 12 | 13 |
14 | 15 | 17 | 18 |

Webpack Plugin Import Weapp Component

19 |

Import wechat app native component

20 |
21 | 22 |

Install

23 | 24 | ```bash 25 | npm i -D import-weapp-component 26 | ``` 27 | 28 |

Usage

29 | 30 | **webpack.config.js** 31 | ```js 32 | const ImportComponent = require('import-weapp-component') 33 | 34 | const config = { 35 | plugins: [ 36 | new ImportComponent({ 37 | src: path.resolve(__dirname, '../src'), // 引用组件或原生页面的目录 38 | native: true, // 将 src 目录中的原生 page 复制到 dist 39 | usingComponents: path.resolve(__dirname, '../src/page.js') // mpvue-entry 配置路径 40 | }) 41 | ] 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '6' 4 | - nodejs_version: '4' 5 | install: 6 | - ps: Install-Product node $env:nodejs_version 7 | - npm install 8 | matrix: 9 | fast_finish: true 10 | build: off 11 | shallow_clone: true 12 | test_script: 13 | - npm run test 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "import-weapp-component", 3 | "version": "4.7.3", 4 | "description": "import weapp native component for mpvue", 5 | "author": "Jesse Yang", 6 | "license": "MIT", 7 | "main": "dist/index.js", 8 | "engines": { 9 | "node": ">= 6" 10 | }, 11 | "files": [ 12 | "dist" 13 | ], 14 | "scripts": { 15 | "lint": "eslint src/ tests/", 16 | "prepare": "npm run build", 17 | "release": "standard-version", 18 | "pretest": "npm run lint && npm run build && npm run build:tests", 19 | "test": "mocha compiled_tests/", 20 | "build": "babel src/ --out-dir dist/", 21 | "build:tests": "babel tests/ --out-dir compiled_tests/ && rimraf compiled_tests/helpers && ncp tests/helpers compiled_tests/helpers && ncp tests/weappHelpers compiled_tests/weappHelpers && node scripts/createSpecialDirectory.js" 22 | }, 23 | "dependencies": { 24 | "acorn": "^6.0.2", 25 | "cacache": "^10.0.4", 26 | "estree-walker": "^0.5.2", 27 | "find-cache-dir": "^1.0.0", 28 | "globby": "^7.1.1", 29 | "is-glob": "^4.0.0", 30 | "loader-utils": "^1.1.0", 31 | "minimatch": "^3.0.4", 32 | "p-limit": "^1.0.0", 33 | "serialize-javascript": "^1.4.0" 34 | }, 35 | "devDependencies": { 36 | "babel-cli": "^6.8.0", 37 | "babel-preset-es2015": "^6.6.0", 38 | "chai": "^3.4.0", 39 | "enhanced-resolve": "^3.4.1", 40 | "eslint": "^2.9.0", 41 | "is-gzip": "^2.0.0", 42 | "mkdirp": "^0.5.1", 43 | "mocha": "^2.4.5", 44 | "ncp": "^2.0.0", 45 | "rimraf": "^2.6.2", 46 | "standard-version": "^4.2.0" 47 | }, 48 | "homepage": "https://github.com/JJJYY/import-weapp-component", 49 | "bugs": "https://github.com/JJJYY/import-weapp-component/issues", 50 | "repository": "git@github.com:JJJYY/import-weapp-component.git", 51 | "keywords": [ 52 | "webpack", 53 | "plugin", 54 | "import", 55 | "weapp", 56 | "wechat", 57 | "component" 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /scripts/createSpecialDirectory.js: -------------------------------------------------------------------------------- 1 | const mkdirp = require('mkdirp'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const removeIllegalCharacterForWindows = require('../tests/utils/removeIllegalCharacterForWindows'); 5 | 6 | const baseDir = 'compiled_tests/helpers'; 7 | 8 | const specialFiles = { 9 | '[special?directory]/nested/nestedfile.txt': '', 10 | '[special?directory]/(special-*file).txt': 'special', 11 | '[special?directory]/directoryfile.txt': 'new' 12 | }; 13 | 14 | Object.keys(specialFiles).forEach(function (originFile) { 15 | const file = removeIllegalCharacterForWindows(originFile); 16 | const dir = path.dirname(file); 17 | mkdirp.sync(path.join(baseDir, dir)); 18 | fs.writeFileSync(path.join(baseDir, file), specialFiles[originFile]); 19 | }); 20 | 21 | -------------------------------------------------------------------------------- /src/copyWebpackPlugin.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import preProcessPattern from './preProcessPattern'; 3 | import processPattern from './processPattern'; 4 | import extractComponent from './extractComponent'; 5 | 6 | function CopyWebpackPlugin(patterns = [], options = {}, componentConfig = {}) { 7 | if (!Array.isArray(patterns)) { 8 | throw new Error('[copy-webpack-plugin] patterns must be an array'); 9 | } 10 | 11 | // Defaults debug level to 'warning' 12 | options.debug = options.debug || 'warning'; 13 | 14 | // Defaults debugging to info if only true is specified 15 | if (options.debug === true) { 16 | options.debug = 'info'; 17 | } 18 | 19 | const debugLevels = ['warning', 'info', 'debug']; 20 | const debugLevelIndex = debugLevels.indexOf(options.debug); 21 | function log(msg, level) { 22 | if (level === 0) { 23 | msg = `WARNING - ${msg}`; 24 | } else { 25 | level = level || 1; 26 | } 27 | if (level <= debugLevelIndex) { 28 | console.log('[copy-webpack-plugin] ' + msg); // eslint-disable-line no-console 29 | } 30 | } 31 | 32 | function warning(msg) { 33 | log(msg, 0); 34 | } 35 | 36 | function info(msg) { 37 | log(msg, 1); 38 | } 39 | 40 | function debug(msg) { 41 | log(msg, 2); 42 | } 43 | 44 | const apply = (compiler) => { 45 | let fileDependencies; 46 | let contextDependencies; 47 | const written = {}; 48 | 49 | let context; 50 | 51 | if (!options.context) { 52 | context = compiler.options.context; 53 | } else if (!path.isAbsolute(options.context)) { 54 | context = path.join(compiler.options.context, options.context); 55 | } else { 56 | context = options.context; 57 | } 58 | 59 | const emit = (compilation, cb) => { 60 | debug('starting emit'); 61 | const callback = () => { 62 | debug('finishing emit'); 63 | cb(); 64 | }; 65 | 66 | fileDependencies = []; 67 | contextDependencies = []; 68 | 69 | const globalRef = { 70 | info, 71 | debug, 72 | warning, 73 | compilation, 74 | written, 75 | fileDependencies, 76 | contextDependencies, 77 | context, 78 | inputFileSystem: compiler.inputFileSystem, 79 | output: compiler.options.output.path, 80 | ignore: options.ignore || [], 81 | copyUnmodified: options.copyUnmodified, 82 | concurrency: options.concurrency 83 | }; 84 | 85 | if (globalRef.output === '/' && 86 | compiler.options.devServer && 87 | compiler.options.devServer.outputPath) { 88 | globalRef.output = compiler.options.devServer.outputPath; 89 | } 90 | 91 | const tasks = []; 92 | patterns = patterns.concat(extractComponent(compilation, componentConfig) || []); 93 | patterns.forEach((pattern) => { 94 | tasks.push( 95 | Promise.resolve() 96 | .then(() => preProcessPattern(globalRef, pattern)) 97 | // Every source (from) is assumed to exist here 98 | .then((pattern) => processPattern(globalRef, pattern)) 99 | ); 100 | }); 101 | 102 | Promise.all(tasks) 103 | .catch((err) => { 104 | compilation.errors.push(err); 105 | }) 106 | .then(() => callback()); 107 | }; 108 | 109 | const afterEmit = (compilation, cb) => { 110 | debug('starting after-emit'); 111 | const callback = () => { 112 | debug('finishing after-emit'); 113 | cb(); 114 | }; 115 | 116 | let compilationFileDependencies; 117 | let addFileDependency; 118 | if (Array.isArray(compilation.fileDependencies)) { 119 | compilationFileDependencies = new Set(compilation.fileDependencies); 120 | addFileDependency = (file) => compilation.fileDependencies.push(file); 121 | } else { 122 | compilationFileDependencies = compilation.fileDependencies; 123 | addFileDependency = (file) => compilation.fileDependencies.add(file); 124 | } 125 | 126 | let compilationContextDependencies; 127 | let addContextDependency; 128 | if (Array.isArray(compilation.contextDependencies)) { 129 | compilationContextDependencies = new Set(compilation.contextDependencies); 130 | addContextDependency = (file) => compilation.contextDependencies.push(file); 131 | } else { 132 | compilationContextDependencies = compilation.contextDependencies; 133 | addContextDependency = (file) => compilation.contextDependencies.add(file); 134 | } 135 | 136 | // Add file dependencies if they're not already tracked 137 | for (const file of fileDependencies) { 138 | if (compilationFileDependencies.has(file)) { 139 | debug(`not adding ${file} to change tracking, because it's already tracked`); 140 | } else { 141 | debug(`adding ${file} to change tracking`); 142 | addFileDependency(file); 143 | } 144 | } 145 | 146 | // Add context dependencies if they're not already tracked 147 | for (const context of contextDependencies) { 148 | if (compilationContextDependencies.has(context)) { 149 | debug(`not adding ${context} to change tracking, because it's already tracked`); 150 | } else { 151 | debug(`adding ${context} to change tracking`); 152 | addContextDependency(context); 153 | } 154 | } 155 | 156 | callback(); 157 | }; 158 | 159 | if (compiler.hooks) { 160 | const plugin = { name: 'CopyPlugin' }; 161 | 162 | compiler.hooks.emit.tapAsync(plugin, emit); 163 | compiler.hooks.afterEmit.tapAsync(plugin, afterEmit); 164 | } else { 165 | compiler.plugin('emit', emit); 166 | compiler.plugin('after-emit', afterEmit); 167 | } 168 | }; 169 | 170 | return { 171 | apply 172 | }; 173 | } 174 | 175 | CopyWebpackPlugin['default'] = CopyWebpackPlugin; 176 | module.exports = CopyWebpackPlugin; 177 | -------------------------------------------------------------------------------- /src/extractComponent.js: -------------------------------------------------------------------------------- 1 | import globby from 'globby'; 2 | import path, { isAbsolute, resolve, join, relative } from 'path'; 3 | import fs from 'fs'; 4 | import fetchModules from './fetchModules'; 5 | 6 | let globalCompilation; // 主要用于`errors.push` 7 | 8 | const dependencies = new Set(); 9 | 10 | function isPlainObject (_any) { 11 | return Object.prototype.toString.call(_any) === '[object Object]'; 12 | } 13 | 14 | const jsonRE = /\/.+\.json$/; 15 | function getPathParse (filePath) { 16 | return path.parse(filePath); 17 | } 18 | function getFileDir (path) { // must be json file 19 | return getPathParse(path).dir; 20 | } 21 | // function getFileName (path) { 22 | // return getPathParse(path).name; 23 | // } 24 | 25 | function pushError (err) { 26 | err = typeof err === 'string' ? new Error(err) : err; 27 | globalCompilation.errors.push(err); 28 | } 29 | 30 | function getUsingComponents (content, filePath) { 31 | try { 32 | let json = JSON.parse(content); 33 | return json['usingComponents'] || {}; // 防止返回undefined 34 | } catch (e) { 35 | pushError(`${filePath} is not json`); 36 | return {}; // 解析出错,返回空 37 | } 38 | } 39 | 40 | function addComponents (content, components, parent) { 41 | components.push(...Object.keys(content).map(name => parent ? path.join(parent, content[name]) : content[name])); 42 | } 43 | 44 | function addComponentsFromJson (json, components, parent, filePath) { 45 | const content = getUsingComponents(json, filePath); 46 | addComponents(content, components, parent); 47 | } 48 | 49 | function addComponentsFromPath (path, components, parent) { 50 | if (fs.existsSync(path)) { 51 | let json = fs.readFileSync(path, 'utf8'); 52 | addComponentsFromJson(json, components, parent, path); 53 | } else { 54 | pushError(`Component is not found in path "${path}"(not found json)`); 55 | } 56 | } 57 | 58 | function getNativePattern (from, to) { 59 | return { 60 | from: getFileDir(from), 61 | to: getFileDir(to), 62 | ignore: [ '**/*.!(js|json|wxss|wxml)' ] 63 | }; 64 | } 65 | 66 | function generatorPattern (from, to, components, parent) { 67 | const { dir: fromDir, name: fromName } = getPathParse(from); 68 | const filePath = `${from}.js`; 69 | 70 | // 为了与小程序保持一致,仅判断`from.js`是否存在 71 | if (fs.existsSync(filePath)) { 72 | // 读取需要复制的 js 文件找出其依赖项,并添加到复制项 73 | dependencies.add(filePath); 74 | addComponentsFromPath(`${fromDir}/${fromName}.json`, components, getFileDir(parent)); 75 | return getNativePattern(from, to); 76 | } else { 77 | pushError(`Component is not found in path "${from}"(not found js)`); 78 | } 79 | } 80 | 81 | function getOutputDir (file, path) { 82 | const fileDir = getFileDir(file); 83 | const to = isAbsolute(path) 84 | ? path 85 | : join(`${isAbsolute(fileDir) ? fileDir : ('/' + fileDir)}`, path); // 以输出路径为最上级路径 86 | 87 | return to.slice(1); 88 | } 89 | 90 | function getEntry (name, code, context) { 91 | return { 92 | context, 93 | assets: { 94 | [name]: { 95 | source () { 96 | return code; 97 | } 98 | } 99 | } 100 | }; 101 | } 102 | 103 | function path2Entry (_path, context) { 104 | const parse = getPathParse(_path); 105 | const code = fs.readFileSync(_path, { encoding: 'utf8' }); 106 | 107 | if (context) { 108 | _path = relative(context, _path); 109 | } 110 | return getEntry(_path, code, parse.dir); 111 | } 112 | 113 | function getAllExtFileFromSrc (src, ext, getEntry) { 114 | if (!Array.isArray(src)) { 115 | src = [ src ]; 116 | } 117 | return src.reduce((result, dir) => { 118 | let res = []; 119 | const stat = fs.statSync(dir); 120 | if (stat.isDirectory) { 121 | const jsonPaths = globby.sync(resolve(dir, `**/*.${ext}`)); 122 | res = jsonPaths.map((_path) => getEntry ? path2Entry(_path, dir) : _path); 123 | } else { 124 | if (jsonRE.test(dir)) { 125 | res = [ 126 | getEntry ? path2Entry(dir) : dir 127 | ]; 128 | } else { 129 | pushError(`includes: "${dir}", is not a effective path.`); 130 | } 131 | } 132 | 133 | return result.concat(res); 134 | }, []); 135 | } 136 | 137 | // 从 object 或 array 中获取所有 usingComponent 138 | // allUsingComponents 可能为 string 或 object 139 | function getAllUsing (allUsingComponents, entries = [], context = '') { 140 | if (Array.isArray(allUsingComponents)) { 141 | allUsingComponents.forEach(item => getAllUsing(item, entries, context)); 142 | } else if (isPlainObject(allUsingComponents)) { 143 | Object.keys(allUsingComponents).forEach(key => { 144 | const value = allUsingComponents[key]; 145 | if (key === 'usingComponents') { 146 | const file = context + '.json'; 147 | const parse = getPathParse(file); 148 | entries.push(getEntry(file, JSON.stringify({ usingComponents: value }), parse.dir)); 149 | } else if (key === 'path') { 150 | context = value; 151 | } else { 152 | getAllUsing(value, entries, context); 153 | } 154 | }); 155 | } 156 | 157 | return entries; 158 | } 159 | 160 | function hadExist (patterns, pattern) { 161 | return patterns.some(p => p.from === pattern.from && p.to === pattern.to); 162 | } 163 | 164 | export default function extractComponent (compilation, componentConfig = {}) { 165 | dependencies.clear(); 166 | 167 | globalCompilation = compilation; 168 | let patterns = []; 169 | 170 | let entries = compilation.entries ? compilation.entries.slice(0) : []; 171 | 172 | let projectContext = (compilation.options || {}).context || ''; 173 | const { src, native, usingComponents } = componentConfig; 174 | 175 | // 增加对最新版本 mpvue 的支持 176 | if (src) { 177 | entries = entries.concat(getAllExtFileFromSrc(src, 'json', true)); 178 | } 179 | 180 | if (usingComponents) { 181 | try { 182 | const allUsingComponents = require(usingComponents); 183 | entries = entries.concat(getAllUsing(allUsingComponents)); 184 | // 修改 projectContext 185 | projectContext = getPathParse(usingComponents).dir; 186 | } catch (e) { 187 | pushError(e); 188 | } 189 | } 190 | 191 | if (native && projectContext) { 192 | const nativePages = getAllExtFileFromSrc(src, 'wxml'); 193 | nativePages.forEach(dir => { 194 | const parse = getPathParse(dir); 195 | const filePath = `${parse.dir}/${parse.name}.js`; 196 | const jsonPath = `${parse.dir}/${parse.name}.json`; 197 | if (fs.existsSync(filePath)) { 198 | if (fs.existsSync(jsonPath)) { 199 | let json = fs.readFileSync(jsonPath, { encoding: 'utf8' }); 200 | if (json) { 201 | json = JSON.parse(json); 202 | // 剔除组件 203 | if (json.component) { 204 | return; 205 | } 206 | } 207 | } 208 | dependencies.add(filePath); 209 | const to = relative(src, dir); 210 | 211 | patterns.push(getNativePattern(dir, to)); 212 | } 213 | }); 214 | } 215 | 216 | if (entries.length && projectContext) { 217 | for (let i = 0; i < entries.length; i++) { 218 | let context = entries[i].context; 219 | let assets = entries[i].assets; 220 | 221 | // 从 assets 获取所有 components 路径 222 | const file = Object.keys(assets).find(f => jsonRE.test(f)); 223 | let components = []; 224 | if (file) { 225 | addComponentsFromJson(assets[file].source(), components, null, file); 226 | } 227 | 228 | for (let j = 0; j < components.length; j++) { 229 | const path = components[j]; 230 | if (path) { 231 | const from = isAbsolute(path) 232 | ? join(projectContext, path) 233 | : resolve(projectContext, context, path); 234 | 235 | const to = getOutputDir(file, path); 236 | 237 | const pattern = generatorPattern(from, to, components, components[j]); 238 | // 重复去重 239 | if (pattern && !hadExist(patterns, pattern)) patterns.push(pattern); 240 | } 241 | } 242 | } 243 | 244 | // 根据 dependencies 列表得到需要复制的依赖文件 245 | patterns = patterns.concat(fetchModules(dependencies, projectContext)); 246 | } 247 | 248 | return patterns; 249 | } 250 | -------------------------------------------------------------------------------- /src/fetchModules.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { parse } from 'acorn'; 4 | import { walk } from 'estree-walker'; 5 | 6 | 7 | function isDependency (node) { 8 | return node.type === 'ImportDeclaration' 9 | || (node.type === 'Identifier' && node.name === 'require'); 10 | } 11 | 12 | function getFilePath (origin, filePath, projectContext) { 13 | if (path.isAbsolute(filePath)) { 14 | return path.join(projectContext, filePath); 15 | } else { 16 | const dir = path.parse(origin).dir; 17 | return path.join(dir, filePath); 18 | } 19 | } 20 | 21 | const jsFileReg = /\.js$/; 22 | function fileExist (filePath) { 23 | if (jsFileReg.test(filePath)) return filePath; 24 | 25 | if (fs.existsSync(`${filePath}.js`)) { 26 | return `${filePath}.js`; 27 | } 28 | if (fs.existsSync(`${filePath}/index.js`)) { 29 | return `${filePath}/index.js`; 30 | } 31 | } 32 | 33 | function generatorPatterns (filePaths, projectContext) { 34 | const patterns = []; 35 | for (let i = 0; i < filePaths.length; i++) { 36 | const toPath = path.relative(projectContext, filePaths[i]); 37 | patterns.push({ 38 | from: filePaths[i], 39 | to: toPath 40 | }); 41 | } 42 | 43 | return patterns; 44 | } 45 | 46 | export default function fetchModules (filePaths, projectContext) { 47 | const filePathArr = [...filePaths]; 48 | const patterns = []; 49 | const len = filePathArr.length; 50 | 51 | for (let i = 0; i < filePathArr.length; i++) { 52 | const origin = filePathArr[i] = fileExist(filePathArr[i]); 53 | if (!origin) continue; 54 | const content = fs.readFileSync(origin, { encoding: 'utf8' }); 55 | const ast = parse(content, { sourceType: 'module' }); 56 | walk(ast, { 57 | enter (node, parent) { 58 | if (isDependency(node)) { 59 | let dep = ''; 60 | if (node.type === 'ImportDeclaration') { 61 | dep = node.source.value; 62 | } else { 63 | dep = parent.arguments[0] ? parent.arguments[0].value : ''; 64 | } 65 | 66 | dep = getFilePath(origin, dep, projectContext); 67 | filePathArr.push(dep); 68 | } 69 | } 70 | }); 71 | } 72 | 73 | const deps = generatorPatterns(filePathArr.slice(len), projectContext); 74 | 75 | return patterns.concat(deps); 76 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import CopyWebpackPlugin from './copyWebpackPlugin'; 2 | 3 | let ImportComponent = CopyWebpackPlugin.bind(null, [], {}); 4 | ImportComponent['default'] = ImportComponent; 5 | module.exports = ImportComponent; -------------------------------------------------------------------------------- /src/preProcessPattern.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import isGlob from 'is-glob'; 3 | import escape from './utils/escape'; 4 | import isObject from './utils/isObject'; 5 | import { stat } from './utils/promisify'; 6 | 7 | // https://www.debuggex.com/r/VH2yS2mvJOitiyr3 8 | const isTemplateLike = /(\[ext\])|(\[name\])|(\[path\])|(\[folder\])|(\[emoji(:\d+)?\])|(\[(\w+:)?hash(:\w+)?(:\d+)?\])|(\[\d+\])/; 9 | 10 | export default function preProcessPattern(globalRef, pattern) { 11 | const {info, debug, warning, context, inputFileSystem, 12 | fileDependencies, contextDependencies, compilation} = globalRef; 13 | 14 | pattern = typeof pattern === 'string' ? { 15 | from: pattern 16 | } : Object.assign({}, pattern); 17 | pattern.to = pattern.to || ''; 18 | pattern.context = pattern.context || context; 19 | if (!path.isAbsolute(pattern.context)) { 20 | pattern.context = path.join(context, pattern.context); 21 | } 22 | pattern.ignore = globalRef.ignore.concat(pattern.ignore || []); 23 | 24 | info(`processing from: '${pattern.from}' to: '${pattern.to}'`); 25 | 26 | switch(true) { 27 | case !!pattern.toType: // if toType already exists 28 | break; 29 | case isTemplateLike.test(pattern.to): 30 | pattern.toType = 'template'; 31 | break; 32 | case path.extname(pattern.to) === '' || pattern.to.slice(-1) === '/': 33 | pattern.toType = 'dir'; 34 | break; 35 | default: 36 | pattern.toType = 'file'; 37 | } 38 | 39 | debug(`determined '${pattern.to}' is a '${pattern.toType}'`); 40 | 41 | // If we know it's a glob, then bail early 42 | if (isObject(pattern.from) && pattern.from.glob) { 43 | pattern.fromType = 'glob'; 44 | 45 | const fromArgs = Object.assign({}, pattern.from); 46 | delete fromArgs.glob; 47 | 48 | pattern.fromArgs = fromArgs; 49 | pattern.glob = escape(pattern.context, pattern.from.glob); 50 | pattern.absoluteFrom = path.resolve(pattern.context, pattern.from.glob); 51 | return Promise.resolve(pattern); 52 | } 53 | 54 | if (path.isAbsolute(pattern.from)) { 55 | pattern.absoluteFrom = pattern.from; 56 | } else { 57 | pattern.absoluteFrom = path.resolve(pattern.context, pattern.from); 58 | } 59 | 60 | debug(`determined '${pattern.from}' to be read from '${pattern.absoluteFrom}'`); 61 | 62 | return stat(inputFileSystem, pattern.absoluteFrom) 63 | .catch(() => { 64 | // If from doesn't appear to be a glob, then log a warning 65 | if (isGlob(pattern.from) || pattern.from.indexOf('*') !== -1) { 66 | pattern.fromType = 'glob'; 67 | pattern.glob = escape(pattern.context, pattern.from); 68 | } else { 69 | const msg = `unable to locate '${pattern.from}' at '${pattern.absoluteFrom}'`; 70 | warning(msg); 71 | compilation.errors.push(`[copy-webpack-plugin] ${msg}`); 72 | pattern.fromType = 'nonexistent'; 73 | } 74 | }) 75 | .then((stat) => { 76 | if (!stat) { 77 | return pattern; 78 | } 79 | 80 | if (stat.isDirectory()) { 81 | pattern.fromType = 'dir'; 82 | pattern.context = pattern.absoluteFrom; 83 | contextDependencies.push(pattern.absoluteFrom); 84 | pattern.glob = escape(pattern.absoluteFrom, '**/*'); 85 | pattern.absoluteFrom = path.join(pattern.absoluteFrom, '**/*'); 86 | pattern.fromArgs = { 87 | dot: true 88 | }; 89 | } else if(stat.isFile()) { 90 | pattern.fromType = 'file'; 91 | pattern.context = path.dirname(pattern.absoluteFrom); 92 | pattern.glob = escape(pattern.absoluteFrom); 93 | pattern.fromArgs = { 94 | dot: true 95 | }; 96 | fileDependencies.push(pattern.absoluteFrom); 97 | } else if(!pattern.fromType) { 98 | info(`Unrecognized file type for ${pattern.from}`); 99 | } 100 | return pattern; 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /src/processPattern.js: -------------------------------------------------------------------------------- 1 | import globby from 'globby'; 2 | import pLimit from 'p-limit'; 3 | import path from 'path'; 4 | import minimatch from 'minimatch'; 5 | import writeFile from './writeFile'; 6 | import isObject from './utils/isObject'; 7 | 8 | export default function processPattern(globalRef, pattern) { 9 | const {info, debug, output, concurrency} = globalRef; 10 | const globArgs = Object.assign({ 11 | cwd: pattern.context 12 | }, pattern.fromArgs || {}); 13 | 14 | if (pattern.fromType === 'nonexistent') { 15 | return Promise.resolve(); 16 | } 17 | 18 | const limit = pLimit(concurrency || 100); 19 | 20 | info(`begin globbing '${pattern.glob}' with a context of '${pattern.context}'`); 21 | return globby(pattern.glob, globArgs) 22 | .then((paths) => Promise.all(paths.map((from) => limit(() => { 23 | const file = { 24 | force: pattern.force, 25 | absoluteFrom: path.resolve(pattern.context, from) 26 | }; 27 | file.relativeFrom = path.relative(pattern.context, file.absoluteFrom); 28 | 29 | if (pattern.flatten) { 30 | file.relativeFrom = path.basename(file.relativeFrom); 31 | } 32 | 33 | debug(`found ${from}`); 34 | 35 | // Check the ignore list 36 | let il = pattern.ignore.length; 37 | while (il--) { 38 | const ignoreGlob = pattern.ignore[il]; 39 | 40 | let globParams = { 41 | dot: true, 42 | matchBase: true 43 | }; 44 | 45 | let glob; 46 | if (typeof ignoreGlob === 'string') { 47 | glob = ignoreGlob; 48 | } else if (isObject(ignoreGlob)) { 49 | glob = ignoreGlob.glob || ''; 50 | const ignoreGlobParams = Object.assign({}, ignoreGlob); 51 | delete ignoreGlobParams.glob; 52 | 53 | // Overwrite minimatch defaults 54 | globParams = Object.assign(globParams, ignoreGlobParams); 55 | } else { 56 | glob = ''; 57 | } 58 | 59 | debug(`testing ${glob} against ${file.relativeFrom}`); 60 | if (minimatch(file.relativeFrom, glob, globParams)) { 61 | info(`ignoring '${file.relativeFrom}', because it matches the ignore glob '${glob}'`); 62 | return Promise.resolve(); 63 | } else { 64 | debug(`${glob} doesn't match ${file.relativeFrom}`); 65 | } 66 | } 67 | 68 | // Change the to path to be relative for webpack 69 | if (pattern.toType === 'dir') { 70 | file.webpackTo = path.join(pattern.to, file.relativeFrom); 71 | } else if (pattern.toType === 'file') { 72 | file.webpackTo = pattern.to || file.relativeFrom; 73 | } else if (pattern.toType === 'template') { 74 | file.webpackTo = pattern.to; 75 | file.webpackToRegExp = pattern.test; 76 | } 77 | 78 | if (path.isAbsolute(file.webpackTo)) { 79 | if (output === '/') { 80 | throw '[copy-webpack-plugin] Using older versions of webpack-dev-server, devServer.outputPath must be defined to write to absolute paths'; 81 | } 82 | 83 | file.webpackTo = path.relative(output, file.webpackTo); 84 | } 85 | 86 | // ensure forward slashes 87 | file.webpackTo = file.webpackTo.replace(/\\/g, '/'); 88 | 89 | info(`determined that '${from}' should write to '${file.webpackTo}'`); 90 | 91 | return writeFile(globalRef, pattern, file); 92 | })))); 93 | } 94 | -------------------------------------------------------------------------------- /src/utils/escape.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | export default function escape(context, from) { 4 | if (from && path.isAbsolute(from)) { 5 | return from; 6 | } else { 7 | // Ensure context is escaped before globbing 8 | // Handles special characters in paths 9 | const absoluteContext = path.resolve(context) 10 | .replace(/[\*|\?|\!|\(|\)|\[|\]|\{|\}]/g, (substring) => `[${substring}]`); 11 | 12 | if (!from) { 13 | return absoluteContext; 14 | } 15 | 16 | // Cannot use path.join/resolve as it "fixes" the path separators 17 | if (absoluteContext.endsWith('/')) { 18 | return `${absoluteContext}${from}`; 19 | } else { 20 | return `${absoluteContext}/${from}`; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/utils/isObject.js: -------------------------------------------------------------------------------- 1 | export default (val) => Object.prototype.toString.call(val) === '[object Object]' ? true : false; 2 | -------------------------------------------------------------------------------- /src/utils/promisify.js: -------------------------------------------------------------------------------- 1 | export const stat = (inputFileSystem, path) => { 2 | return new Promise((resolve, reject) => { 3 | inputFileSystem.stat(path, (err, stats) => { 4 | if (err) { 5 | reject(err); 6 | } 7 | resolve(stats); 8 | }); 9 | }); 10 | }; 11 | 12 | export const readFile = (inputFileSystem, path) => { 13 | return new Promise((resolve, reject) => { 14 | inputFileSystem.readFile(path, (err, stats) => { 15 | if (err) { 16 | reject(err); 17 | } 18 | resolve(stats); 19 | }); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /src/writeFile.js: -------------------------------------------------------------------------------- 1 | import loaderUtils from 'loader-utils'; 2 | import path from 'path'; 3 | import cacache from 'cacache'; 4 | import serialize from 'serialize-javascript'; 5 | import { name, version } from '../package.json'; 6 | import findCacheDir from 'find-cache-dir'; 7 | import { stat, readFile } from './utils/promisify'; 8 | import crypto from 'crypto'; 9 | 10 | export default function writeFile(globalRef, pattern, file) { 11 | const {info, debug, compilation, fileDependencies, written, inputFileSystem, copyUnmodified} = globalRef; 12 | 13 | return stat(inputFileSystem, file.absoluteFrom) 14 | .then((stat) => { 15 | // We don't write empty directories 16 | if (stat.isDirectory()) { 17 | return; 18 | } 19 | 20 | // If this came from a glob, add it to the file watchlist 21 | if (pattern.fromType === 'glob') { 22 | fileDependencies.push(file.absoluteFrom); 23 | } 24 | 25 | info(`reading ${file.absoluteFrom} to write to assets`); 26 | return readFile(inputFileSystem, file.absoluteFrom) 27 | .then((content) => { 28 | if (pattern.transform) { 29 | const transform = (content, absoluteFrom) => { 30 | return pattern.transform(content, absoluteFrom); 31 | }; 32 | 33 | if (pattern.cache) { 34 | if (!globalRef.cacheDir) { 35 | globalRef.cacheDir = findCacheDir({ name: 'copy-webpack-plugin' }); 36 | } 37 | 38 | const cacheKey = pattern.cache.key 39 | ? pattern.cache.key 40 | : serialize({ 41 | name, 42 | version, 43 | pattern, 44 | hash: crypto.createHash('md4').update(content).digest('hex') 45 | }); 46 | 47 | return cacache 48 | .get(globalRef.cacheDir, cacheKey) 49 | .then( 50 | (result) => result.data, 51 | () => { 52 | return Promise 53 | .resolve() 54 | .then(() => transform(content, file.absoluteFrom)) 55 | .then((content) => cacache.put(globalRef.cacheDir, cacheKey, content) 56 | .then(() => content)); 57 | } 58 | ); 59 | } 60 | 61 | content = transform(content, file.absoluteFrom); 62 | } 63 | 64 | return content; 65 | }).then((content) => { 66 | const hash = loaderUtils.getHashDigest(content); 67 | 68 | if (pattern.toType === 'template') { 69 | info(`interpolating template '${file.webpackTo}' for '${file.relativeFrom}'`); 70 | 71 | // If it doesn't have an extension, remove it from the pattern 72 | // ie. [name].[ext] or [name][ext] both become [name] 73 | if (!path.extname(file.relativeFrom)) { 74 | file.webpackTo = file.webpackTo.replace(/\.?\[ext\]/g, ''); 75 | } 76 | 77 | file.webpackTo = loaderUtils.interpolateName( 78 | {resourcePath: file.absoluteFrom}, 79 | file.webpackTo, 80 | { 81 | content, 82 | regExp: file.webpackToRegExp, 83 | context: pattern.context 84 | } 85 | ); 86 | } 87 | 88 | if (!copyUnmodified && 89 | written[file.absoluteFrom] && 90 | written[file.absoluteFrom]['hash'] === hash && 91 | written[file.absoluteFrom]['webpackTo'] === file.webpackTo 92 | ) { 93 | info(`skipping '${file.webpackTo}', because it hasn't changed`); 94 | return; 95 | } else { 96 | debug(`added ${hash} to written tracking for '${file.absoluteFrom}'`); 97 | written[file.absoluteFrom] = { 98 | hash: hash, 99 | webpackTo: file.webpackTo 100 | }; 101 | } 102 | 103 | if (compilation.assets[file.webpackTo] && !file.force) { 104 | info(`skipping '${file.webpackTo}', because it already exists`); 105 | return; 106 | } 107 | 108 | info(`writing '${file.webpackTo}' to compilation assets from '${file.absoluteFrom}'`); 109 | compilation.assets[file.webpackTo] = { 110 | size: function() { 111 | return stat.size; 112 | }, 113 | source: function() { 114 | return content; 115 | } 116 | }; 117 | }); 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /tests/components.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import path from 'path'; 3 | import extractComponent from '../dist/extractComponent'; 4 | 5 | function resolve (...paths) { 6 | return path.resolve(__dirname, './weappHelpers', ...paths); 7 | } 8 | 9 | function generatorEntries (pages) { 10 | if (!Array.isArray(pages)) { 11 | pages = [pages]; 12 | } 13 | 14 | return pages.map(page => { 15 | // const _path = parse(page.path); 16 | return { 17 | context: page.context, 18 | assets: { 19 | [page.name]: { 20 | source () { 21 | return JSON.stringify(page.content); 22 | } 23 | } 24 | } 25 | }; 26 | }); 27 | } 28 | 29 | function run (opts) { 30 | return new Promise ((res, rej) => { 31 | const compilation = Object.assign({ 32 | assets: {}, 33 | contextDependencies: [], 34 | errors: [], 35 | fileDependencies: [], 36 | options: { 37 | context: resolve() 38 | } 39 | }, opts.compilation); 40 | 41 | const patterns = extractComponent(compilation, opts.config); 42 | console.log(patterns); 43 | 44 | let expectPatterns = opts.components.map(comp => { 45 | return { 46 | from: resolve(comp), 47 | to: comp, 48 | ignore: [ '**/*.!(js|json|wxss|wxml)' ] 49 | }; 50 | }); 51 | if (opts.dependencies) { 52 | expectPatterns = expectPatterns.concat(opts.dependencies); 53 | } 54 | if (expect(patterns).to.have.deep.members(expectPatterns)) { 55 | res(); 56 | } else { 57 | rej(); 58 | } 59 | }); 60 | } 61 | 62 | describe('should get error', () => { 63 | it('component not exist', (done) => { 64 | const components = [ 65 | ]; 66 | const compilation = { 67 | errors: [], 68 | entries: generatorEntries([ 69 | { 70 | name: 'pages/self/index.json', 71 | context: resolve('pages/self'), 72 | content: { 73 | usingComponents: { 74 | none: '/components/none/index' 75 | } 76 | } 77 | } 78 | ]) 79 | }; 80 | run({ 81 | compilation, 82 | components 83 | }) 84 | .then(() => { 85 | done(compilation.errors.length !== 0 ? null : new Error('not get error')); 86 | }) 87 | .catch(done); 88 | }); 89 | }); 90 | 91 | // 目录中使用单独的 json 文件来模拟 mpvue-loader 在 emit 生成的 json 文件 92 | // 低版本 mpvue-loader 93 | describe('generator patterns when emit', () => { 94 | describe('single pages', () => { 95 | it('relative path', (done) => { 96 | const components = [ 'components/button' ]; 97 | run({ 98 | compilation: { 99 | entries: generatorEntries([ 100 | { 101 | name: 'pages/self/index.json', 102 | context: resolve('pages/self'), 103 | content: { 104 | usingComponents: { 105 | button: '/components/button/index' 106 | } 107 | } 108 | } 109 | ]) 110 | }, 111 | components 112 | }) 113 | .then(done) 114 | .catch(done); 115 | }); 116 | 117 | it('absolute path and import component from other component', (done) => { 118 | const components = [ 119 | 'components/button', 120 | 'components/panel' 121 | ]; 122 | run({ 123 | compilation: { 124 | entries: generatorEntries([ 125 | { 126 | name: 'pages/normal/index.json', 127 | context: resolve('pages/normal'), 128 | content: { 129 | usingComponents: { 130 | panel: '/components/panel/index' 131 | } 132 | } 133 | } 134 | ]) 135 | }, 136 | components 137 | }) 138 | .then(done) 139 | .catch(done); 140 | }); 141 | }); 142 | 143 | describe('multiply pages', () => { 144 | it('repeat import component', (done) => { 145 | const components = [ 146 | 'components/button', 147 | 'components/panel' 148 | ]; 149 | run({ 150 | compilation: { 151 | entries: generatorEntries([ 152 | { 153 | name: 'pages/self/index.json', 154 | context: resolve('pages/self'), 155 | content: { 156 | usingComponents: { 157 | button: '/components/button/index' 158 | } 159 | } 160 | }, 161 | { 162 | name: 'pages/normal/index.json', 163 | context: resolve('pages/normal'), 164 | content: { 165 | usingComponents: { 166 | panel: '/components/panel/index' 167 | } 168 | } 169 | } 170 | ]) 171 | }, 172 | components 173 | }) 174 | .then(done) 175 | .catch(done); 176 | }); 177 | }); 178 | }); 179 | 180 | // mpvue-loader v1.1.0 以上 181 | describe('generator patterns read json path', () => { 182 | const config = { 183 | src: resolve() 184 | }; 185 | 186 | describe('config src path', () => { 187 | it('relative path', (done) => { 188 | const components = [ 189 | 'components/button', 190 | 'components/panel' 191 | ]; 192 | run({ 193 | components, 194 | config 195 | }) 196 | .then(done) 197 | .catch(done); 198 | }); 199 | }); 200 | 201 | describe('copy native page', () => { 202 | it('relative path', (done) => { 203 | const components = [ 204 | 'components/button', 205 | 'components/panel', 206 | 'pages/native' 207 | ]; 208 | run({ 209 | components, 210 | config: { 211 | src: resolve(), 212 | native: true 213 | } 214 | }) 215 | .then(done) 216 | .catch(done); 217 | }); 218 | }); 219 | }); 220 | 221 | describe('compatible with', () => { 222 | const config = { 223 | usingComponents: resolve('entries.js') 224 | }; 225 | 226 | describe('mpvue-entry', () => { 227 | it('form entries config', (done) => { 228 | const components = [ 229 | 'components/button', 230 | 'components/panel' 231 | ]; 232 | run({ 233 | components, 234 | config 235 | }) 236 | .then(done) 237 | .catch(done); 238 | }); 239 | }); 240 | }); 241 | 242 | describe('component depends other', () => { 243 | describe('require function', () => { 244 | it('from component', (done) => { 245 | const components = [ 246 | 'components/comp1' 247 | ]; 248 | run({ 249 | components, 250 | compilation: { 251 | entries: generatorEntries([ 252 | { 253 | name: 'pages/normal/index.json', 254 | context: resolve('pages/normal'), 255 | content: { 256 | usingComponents: { 257 | panel: '/components/comp1/index' 258 | } 259 | } 260 | } 261 | ]) 262 | }, 263 | dependencies: [ 264 | { 265 | from: resolve('components/base/Page.js'), 266 | to: 'components/base/Page.js' 267 | }, 268 | { 269 | from: resolve('components/base/Dep/index.js'), 270 | to: 'components/base/Dep/index.js' 271 | } 272 | ] 273 | }) 274 | .then(done) 275 | .catch(done); 276 | }); 277 | }); 278 | }); 279 | -------------------------------------------------------------------------------- /tests/helpers/[!]/hello.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessejyang/import-weapp-component/3e70cd33b6097d155893f7556435a42345c51de8/tests/helpers/[!]/hello.txt -------------------------------------------------------------------------------- /tests/helpers/binextension.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessejyang/import-weapp-component/3e70cd33b6097d155893f7556435a42345c51de8/tests/helpers/binextension.bin -------------------------------------------------------------------------------- /tests/helpers/directory/.dottedfile: -------------------------------------------------------------------------------- 1 | dottedfile contents 2 | -------------------------------------------------------------------------------- /tests/helpers/directory/directoryfile.txt: -------------------------------------------------------------------------------- 1 | new -------------------------------------------------------------------------------- /tests/helpers/directory/nested/nestedfile.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessejyang/import-weapp-component/3e70cd33b6097d155893f7556435a42345c51de8/tests/helpers/directory/nested/nestedfile.txt -------------------------------------------------------------------------------- /tests/helpers/file.txt: -------------------------------------------------------------------------------- 1 | new -------------------------------------------------------------------------------- /tests/helpers/file.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessejyang/import-weapp-component/3e70cd33b6097d155893f7556435a42345c51de8/tests/helpers/file.txt.gz -------------------------------------------------------------------------------- /tests/helpers/noextension: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessejyang/import-weapp-component/3e70cd33b6097d155893f7556435a42345c51de8/tests/helpers/noextension -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it, __dirname */ 2 | import { 3 | expect 4 | } from 'chai'; 5 | import NodeJsInputFileSystem from 'enhanced-resolve/lib/NodeJsInputFileSystem'; 6 | import CachedInputFileSystem from 'enhanced-resolve/lib/CachedInputFileSystem'; 7 | 8 | // ensure we don't mess up classic imports 9 | const CopyWebpackPlugin = require('./../dist/copyWebpackPlugin'); 10 | 11 | import fs from 'fs'; 12 | import path from 'path'; 13 | import findCacheDir from 'find-cache-dir'; 14 | import cacache from 'cacache'; 15 | import isGzip from 'is-gzip'; 16 | import zlib from 'zlib'; 17 | 18 | import removeIllegalCharacterForWindows from './utils/removeIllegalCharacterForWindows'; 19 | 20 | const BUILD_DIR = path.join(__dirname, 'build'); 21 | const HELPER_DIR = path.join(__dirname, 'helpers'); 22 | const TEMP_DIR = path.join(__dirname, 'tempdir'); 23 | 24 | class MockCompiler { 25 | constructor (options = {}) { 26 | this.options = { 27 | context: HELPER_DIR, 28 | output: { 29 | path: options.outputPath || BUILD_DIR 30 | } 31 | }; 32 | 33 | if (options.devServer && options.devServer.outputPath) { 34 | this.options.devServer = { 35 | outputPath: options.devServer.outputPath 36 | }; 37 | } 38 | 39 | this.inputFileSystem = new CachedInputFileSystem(new NodeJsInputFileSystem(), 0); 40 | 41 | this.outputFileSystem = { 42 | constructor: { 43 | name: 'NotMemoryFileSystem' 44 | } 45 | }; 46 | } 47 | 48 | plugin (type, fn) { 49 | if (type === 'emit') { 50 | this.emitFn = fn; 51 | } 52 | 53 | if (type === 'after-emit') { 54 | this.afterEmitFn = fn; 55 | } 56 | } 57 | } 58 | 59 | describe('apply function', () => { 60 | // Ideally we pass in patterns and confirm the resulting assets 61 | const run = (opts) => { 62 | return new Promise((resolve, reject) => { 63 | if (Array.isArray(opts.patterns)) { 64 | opts.patterns.forEach(function (pattern) { 65 | if (pattern.context) { 66 | pattern.context = removeIllegalCharacterForWindows(pattern.context); 67 | } 68 | }); 69 | } 70 | const plugin = CopyWebpackPlugin(opts.patterns, opts.options); 71 | 72 | // Get a mock compiler to pass to plugin.apply 73 | const compiler = opts.compiler || new MockCompiler(); 74 | 75 | plugin.apply(compiler); 76 | 77 | // Call the registered function with a mock compilation and callback 78 | const compilation = Object.assign({ 79 | assets: {}, 80 | contextDependencies: [], 81 | errors: [], 82 | fileDependencies: [] 83 | }, opts.compilation); 84 | 85 | // Execute the functions in series 86 | return Promise.resolve() 87 | .then(() => { 88 | return new Promise((res, rej) => { 89 | try { 90 | compiler.emitFn(compilation, res); 91 | } catch (error) { 92 | rej(error); 93 | } 94 | }); 95 | }) 96 | .then(() => { 97 | return new Promise((res, rej) => { 98 | try { 99 | compiler.afterEmitFn(compilation, res); 100 | } catch (error) { 101 | rej(error); 102 | } 103 | }); 104 | }) 105 | .then(() => { 106 | if (opts.expectedErrors) { 107 | expect(compilation.errors).to.deep.equal(opts.expectedErrors); 108 | } else if (compilation.errors.length > 0) { 109 | throw compilation.errors[0]; 110 | } 111 | resolve(compilation); 112 | }) 113 | .catch(reject); 114 | }); 115 | }; 116 | 117 | const runEmit = (opts) => { 118 | return run(opts) 119 | .then((compilation) => { 120 | if (opts.expectedAssetKeys && opts.expectedAssetKeys.length > 0) { 121 | expect(compilation.assets).to.have.all.keys(opts.expectedAssetKeys.map(removeIllegalCharacterForWindows)); 122 | } else { 123 | expect(compilation.assets).to.deep.equal({}); 124 | } 125 | 126 | if (opts.expectedAssetContent) { 127 | for (var key in opts.expectedAssetContent) { 128 | expect(compilation.assets[key]).to.exist; 129 | if (compilation.assets[key]) { 130 | let expectedContent = opts.expectedAssetContent[key]; 131 | 132 | if (!Buffer.isBuffer(expectedContent)) { 133 | expectedContent = new Buffer(expectedContent); 134 | } 135 | 136 | let compiledContent = compilation.assets[key].source(); 137 | 138 | if (!Buffer.isBuffer(compiledContent)) { 139 | compiledContent = new Buffer(compiledContent); 140 | } 141 | 142 | expect(Buffer.compare(expectedContent, compiledContent)).to.equal(0); 143 | } 144 | } 145 | } 146 | }); 147 | }; 148 | 149 | const runForce = (opts) => { 150 | opts.compilation = { 151 | assets: {} 152 | }; 153 | opts.compilation.assets[opts.existingAsset] = { 154 | source () { 155 | return 'existing'; 156 | } 157 | }; 158 | 159 | return run(opts).then(() => {}); 160 | }; 161 | 162 | const runChange = (opts) => { 163 | // Create two test files 164 | fs.writeFileSync(opts.newFileLoc1, 'file1contents'); 165 | fs.writeFileSync(opts.newFileLoc2, 'file2contents'); 166 | 167 | // Create a reference to the compiler 168 | const compiler = new MockCompiler(); 169 | const compilation = { 170 | assets: {}, 171 | contextDependencies: [], 172 | errors: [], 173 | fileDependencies: [] 174 | }; 175 | 176 | return run({ 177 | compiler, 178 | options: opts.options, 179 | patterns: opts.patterns 180 | }) 181 | .then(() => { 182 | // Change a file 183 | fs.appendFileSync(opts.newFileLoc1, 'extra'); 184 | 185 | // Trigger another compile 186 | return new Promise((res) => { 187 | compiler.emitFn(compilation, res); 188 | }); 189 | }) 190 | .then(() => { 191 | if (opts.expectedAssetKeys && opts.expectedAssetKeys.length > 0) { 192 | expect(compilation.assets).to.have.all.keys(opts.expectedAssetKeys); 193 | } else { 194 | expect(compilation.assets).to.deep.equal({}); 195 | } 196 | }) 197 | .then(() => { 198 | fs.unlinkSync(opts.newFileLoc1); 199 | fs.unlinkSync(opts.newFileLoc2); 200 | }); 201 | }; 202 | 203 | // Use then and catch explicitly, so errors 204 | // aren't seen as unhandled exceptions 205 | describe('error handling', () => { 206 | it('doesn\'t throw an error if no patterns are passed', (done) => { 207 | runEmit({ 208 | expectedAssetKeys: [], 209 | patterns: undefined // eslint-disable-line no-undefined 210 | }) 211 | .then(done) 212 | .catch(done); 213 | }); 214 | 215 | it('throws an error if the patterns are an object', () => { 216 | const createPluginWithObject = () => { 217 | CopyWebpackPlugin({}); 218 | }; 219 | 220 | expect(createPluginWithObject).to.throw(Error); 221 | }); 222 | 223 | it('throws an error if the patterns are null', () => { 224 | const createPluginWithNull = () => { 225 | CopyWebpackPlugin(null); 226 | }; 227 | 228 | expect(createPluginWithNull).to.throw(Error); 229 | }); 230 | }); 231 | 232 | describe('with glob in from', () => { 233 | it('can use a glob to move a file to the root directory', (done) => { 234 | runEmit({ 235 | expectedAssetKeys: [ 236 | 'file.txt' 237 | ], 238 | patterns: [{ 239 | from: '*.txt' 240 | }] 241 | }) 242 | .then(done) 243 | .catch(done); 244 | }); 245 | 246 | it('can use a bracketed glob to move a file to the root directory', (done) => { 247 | runEmit({ 248 | expectedAssetKeys: [ 249 | 'directory/directoryfile.txt', 250 | 'directory/nested/nestedfile.txt', 251 | 'file.txt', 252 | 'noextension' 253 | ], 254 | patterns: [{ 255 | from: '{file.txt,noextension,directory/**/*}' 256 | }] 257 | }) 258 | .then(done) 259 | .catch(done); 260 | }); 261 | 262 | it('can use a glob object to move a file to the root directory', (done) => { 263 | runEmit({ 264 | expectedAssetKeys: [ 265 | 'file.txt' 266 | ], 267 | patterns: [{ 268 | from: { 269 | glob: '*.txt' 270 | } 271 | }] 272 | }) 273 | .then(done) 274 | .catch(done); 275 | }); 276 | 277 | it('can use a glob to move multiple files to the root directory', (done) => { 278 | runEmit({ 279 | expectedAssetKeys: [ 280 | '[!]/hello.txt', 281 | 'binextension.bin', 282 | 'file.txt', 283 | 'file.txt.gz', 284 | 'directory/directoryfile.txt', 285 | 'directory/nested/nestedfile.txt', 286 | '[special?directory]/directoryfile.txt', 287 | '[special?directory]/(special-*file).txt', 288 | '[special?directory]/nested/nestedfile.txt', 289 | 'noextension' 290 | ], 291 | patterns: [{ 292 | from: '**/*' 293 | }] 294 | }) 295 | .then(done) 296 | .catch(done); 297 | }); 298 | 299 | it('can use a glob to move multiple files to a non-root directory', (done) => { 300 | runEmit({ 301 | expectedAssetKeys: [ 302 | 'nested/[!]/hello.txt', 303 | 'nested/binextension.bin', 304 | 'nested/file.txt', 305 | 'nested/file.txt.gz', 306 | 'nested/directory/directoryfile.txt', 307 | 'nested/directory/nested/nestedfile.txt', 308 | 'nested/[special?directory]/directoryfile.txt', 309 | 'nested/[special?directory]/(special-*file).txt', 310 | 'nested/[special?directory]/nested/nestedfile.txt', 311 | 'nested/noextension' 312 | ], 313 | patterns: [{ 314 | from: '**/*', 315 | to: 'nested' 316 | }] 317 | }) 318 | .then(done) 319 | .catch(done); 320 | }); 321 | 322 | it('can use a glob to move multiple files in a different relative context to a non-root directory', (done) => { 323 | runEmit({ 324 | expectedAssetKeys: [ 325 | 'nested/directoryfile.txt', 326 | 'nested/nested/nestedfile.txt' 327 | ], 328 | patterns: [{ 329 | context: 'directory', 330 | from: '**/*', 331 | to: 'nested' 332 | }] 333 | }) 334 | .then(done) 335 | .catch(done); 336 | }); 337 | 338 | it('can use a direct glob to move multiple files in a different relative context with special characters', (done) => { 339 | runEmit({ 340 | expectedAssetKeys: [ 341 | 'directoryfile.txt', 342 | '(special-*file).txt', 343 | 'nested/nestedfile.txt' 344 | ], 345 | patterns: [{ 346 | context: '[special?directory]', 347 | from: { glob: '**/*' } 348 | }] 349 | }) 350 | .then(done) 351 | .catch(done); 352 | }); 353 | 354 | it('can use a glob to move multiple files in a different relative context with special characters', (done) => { 355 | runEmit({ 356 | expectedAssetKeys: [ 357 | 'directoryfile.txt', 358 | '(special-*file).txt', 359 | 'nested/nestedfile.txt' 360 | ], 361 | patterns: [{ 362 | context: '[special?directory]', 363 | from: '**/*' 364 | }] 365 | }) 366 | .then(done) 367 | .catch(done); 368 | }); 369 | 370 | it('can use a glob to flatten multiple files in a relative context to a non-root directory', (done) => { 371 | runEmit({ 372 | expectedAssetKeys: [ 373 | 'nested/directoryfile.txt', 374 | 'nested/nestedfile.txt' 375 | ], 376 | patterns: [{ 377 | context: 'directory', 378 | flatten: true, 379 | from: '**/*', 380 | to: 'nested' 381 | }] 382 | }) 383 | .then(done) 384 | .catch(done); 385 | }); 386 | 387 | it('can use a glob to move multiple files in a different absolute context to a non-root directory', (done) => { 388 | runEmit({ 389 | expectedAssetKeys: [ 390 | 'nested/directoryfile.txt', 391 | 'nested/nested/nestedfile.txt' 392 | ], 393 | patterns: [{ 394 | context: path.join(HELPER_DIR, 'directory'), 395 | from: '**/*', 396 | to: 'nested' 397 | }] 398 | }) 399 | .then(done) 400 | .catch(done); 401 | }); 402 | 403 | it('can use a glob with a full path to move a file to the root directory', (done) => { 404 | runEmit({ 405 | expectedAssetKeys: [ 406 | 'file.txt' 407 | ], 408 | patterns: [{ 409 | from: path.join(HELPER_DIR, '*.txt') 410 | }] 411 | }) 412 | .then(done) 413 | .catch(done); 414 | }); 415 | 416 | it('can use a glob with a full path to move multiple files to the root directory', (done) => { 417 | runEmit({ 418 | expectedAssetKeys: [ 419 | '[!]/hello.txt', 420 | 'file.txt', 421 | 'directory/directoryfile.txt', 422 | 'directory/nested/nestedfile.txt', 423 | '[special?directory]/directoryfile.txt', 424 | '[special?directory]/(special-*file).txt', 425 | '[special?directory]/nested/nestedfile.txt' 426 | ], 427 | patterns: [{ 428 | from: path.join(HELPER_DIR, '**/*.txt') 429 | }] 430 | }) 431 | .then(done) 432 | .catch(done); 433 | }); 434 | 435 | it('can use a glob to move multiple files to a non-root directory with name, hash and ext', (done) => { 436 | runEmit({ 437 | expectedAssetKeys: [ 438 | 'nested/[!]/hello-d41d8c.txt', 439 | 'nested/binextension-d41d8c.bin', 440 | 'nested/file-22af64.txt', 441 | 'nested/file.txt-5b311c.gz', 442 | 'nested/directory/directoryfile-22af64.txt', 443 | 'nested/directory/nested/nestedfile-d41d8c.txt', 444 | 'nested/[special?directory]/(special-*file)-0bd650.txt', 445 | 'nested/[special?directory]/directoryfile-22af64.txt', 446 | 'nested/[special?directory]/nested/nestedfile-d41d8c.txt', 447 | 'nested/noextension-d41d8c' 448 | ], 449 | patterns: [{ 450 | from: '**/*', 451 | to: 'nested/[path][name]-[hash:6].[ext]' 452 | }] 453 | }) 454 | .then(done) 455 | .catch(done); 456 | }); 457 | 458 | it('can flatten or normalize glob matches', (done) => { 459 | runEmit({ 460 | expectedAssetKeys: [ 461 | '[!]-hello.txt', 462 | '[special?directory]-(special-*file).txt', 463 | '[special?directory]-directoryfile.txt', 464 | 'directory-directoryfile.txt' 465 | ], 466 | patterns: [{ 467 | from: '*/*.*', 468 | test: `([^\\${path.sep}]+)\\${path.sep}([^\\${path.sep}]+)\\.\\w+$`, 469 | to: '[1]-[2].[ext]' 470 | }] 471 | }) 472 | .then(done) 473 | .catch(done); 474 | }); 475 | }); 476 | 477 | describe('with file in from', () => { 478 | it('can move a file to the root directory', (done) => { 479 | runEmit({ 480 | expectedAssetKeys: [ 481 | 'file.txt' 482 | ], 483 | patterns: [{ 484 | from: 'file.txt' 485 | }] 486 | }) 487 | .then(done) 488 | .catch(done); 489 | }); 490 | 491 | it('can transform a file', (done) => { 492 | runEmit({ 493 | expectedAssetKeys: [ 494 | 'file.txt' 495 | ], 496 | expectedAssetContent: { 497 | 'file.txt': 'newchanged' 498 | }, 499 | patterns: [{ 500 | from: 'file.txt', 501 | transform: function(content, absoluteFrom) { 502 | expect(absoluteFrom).to.equal(path.join(HELPER_DIR, 'file.txt')); 503 | return content + 'changed'; 504 | } 505 | }] 506 | }) 507 | .then(done) 508 | .catch(done); 509 | }); 510 | 511 | it('warns when file not found', (done) => { 512 | runEmit({ 513 | expectedAssetKeys: [], 514 | expectedErrors: [ 515 | `[copy-webpack-plugin] unable to locate 'nonexistent.txt' at '${HELPER_DIR}${path.sep}nonexistent.txt'` 516 | ], 517 | patterns: [{ 518 | from: 'nonexistent.txt' 519 | }] 520 | }) 521 | .then(done) 522 | .catch(done); 523 | }); 524 | 525 | it('warns when tranform failed', (done) => { 526 | runEmit({ 527 | expectedAssetKeys: [], 528 | expectedErrors: [ 529 | 'a failure happened' 530 | ], 531 | patterns: [{ 532 | from: 'file.txt', 533 | transform: function() { 534 | throw 'a failure happened'; 535 | } 536 | }] 537 | }) 538 | .then(done) 539 | .catch(done); 540 | }); 541 | 542 | it('can use an absolute path to move a file to the root directory', (done) => { 543 | const absolutePath = path.resolve(HELPER_DIR, 'file.txt'); 544 | 545 | runEmit({ 546 | expectedAssetKeys: [ 547 | 'file.txt' 548 | ], 549 | patterns: [{ 550 | from: absolutePath 551 | }] 552 | }) 553 | .then(done) 554 | .catch(done); 555 | }); 556 | 557 | it('can move a file to a new directory without a forward slash', (done) => { 558 | runEmit({ 559 | expectedAssetKeys: [ 560 | 'newdirectory/file.txt' 561 | ], 562 | patterns: [{ 563 | from: 'file.txt', 564 | to: 'newdirectory' 565 | }] 566 | }) 567 | .then(done) 568 | .catch(done); 569 | }); 570 | 571 | it('can move a file to the root directory using an absolute to', (done) => { 572 | runEmit({ 573 | expectedAssetKeys: [ 574 | 'file.txt' 575 | ], 576 | patterns: [{ 577 | from: 'file.txt', 578 | to: BUILD_DIR 579 | }] 580 | }) 581 | .then(done) 582 | .catch(done); 583 | }); 584 | 585 | it('allows absolute to if outpath is defined with webpack-dev-server', (done) => { 586 | runEmit({ 587 | compiler: new MockCompiler({ 588 | outputPath: '/', 589 | devServer: { 590 | outputPath: BUILD_DIR 591 | } 592 | }), 593 | expectedAssetKeys: [ 594 | 'file.txt' 595 | ], 596 | patterns: [{ 597 | from: 'file.txt', 598 | to: BUILD_DIR 599 | }] 600 | }) 601 | .then(done) 602 | .catch(done); 603 | }); 604 | 605 | it('throws an error when output path isn\'t defined with webpack-dev-server', (done) => { 606 | runEmit({ 607 | compiler: new MockCompiler({ 608 | outputPath: '/' 609 | }), 610 | expectedAssetKeys: [], 611 | expectedErrors: [ 612 | '[copy-webpack-plugin] Using older versions of webpack-dev-server, devServer.outputPath must be ' + 613 | 'defined to write to absolute paths' 614 | ], 615 | patterns: [{ 616 | from: 'file.txt', 617 | to: BUILD_DIR 618 | }] 619 | }) 620 | .then(done) 621 | .catch(done); 622 | }); 623 | 624 | it('can move a file to a new directory using an absolute to', (done) => { 625 | runEmit({ 626 | expectedAssetKeys: [ 627 | '../tempdir/file.txt' 628 | ], 629 | patterns: [{ 630 | from: 'file.txt', 631 | to: TEMP_DIR 632 | }] 633 | }) 634 | .then(done) 635 | .catch(done); 636 | }); 637 | 638 | it('can move a file to a new file using an absolute to', (done) => { 639 | const absolutePath = path.resolve(TEMP_DIR, 'newfile.txt'); 640 | 641 | runEmit({ 642 | expectedAssetKeys: [ 643 | '../tempdir/newfile.txt' 644 | ], 645 | patterns: [{ 646 | from: 'file.txt', 647 | to: absolutePath 648 | }] 649 | }) 650 | .then(done) 651 | .catch(done); 652 | }); 653 | 654 | it('can move a file to a new directory with a forward slash', (done) => { 655 | runEmit({ 656 | expectedAssetKeys: [ 657 | 'newdirectory/file.txt' 658 | ], 659 | patterns: [{ 660 | from: 'file.txt', 661 | to: 'newdirectory/' 662 | }] 663 | }) 664 | .then(done) 665 | .catch(done); 666 | }); 667 | 668 | it('can move a file with a context containing special characters', (done) => { 669 | runEmit({ 670 | expectedAssetKeys: [ 671 | 'directoryfile.txt' 672 | ], 673 | patterns: [{ 674 | from: 'directoryfile.txt', 675 | context: '[special?directory]' 676 | }] 677 | }) 678 | .then(done) 679 | .catch(done); 680 | }); 681 | 682 | it('can move a file with special characters with a context containing special characters', (done) => { 683 | runEmit({ 684 | expectedAssetKeys: [ 685 | '(special-*file).txt' 686 | ], 687 | patterns: [{ 688 | from: '(special-*file).txt', 689 | context: '[special?directory]' 690 | }] 691 | }) 692 | .then(done) 693 | .catch(done); 694 | }); 695 | 696 | it('can move a file to a new directory with an extension', (done) => { 697 | runEmit({ 698 | expectedAssetKeys: [ 699 | 'newdirectory.ext/file.txt' 700 | ], 701 | patterns: [{ 702 | from: 'file.txt', 703 | to: 'newdirectory.ext', 704 | toType: 'dir' 705 | }] 706 | }) 707 | .then(done) 708 | .catch(done); 709 | }); 710 | 711 | it('can move a file to a new directory with an extension and forward slash', (done) => { 712 | runEmit({ 713 | expectedAssetKeys: [ 714 | 'newdirectory.ext/file.txt' 715 | ], 716 | patterns: [{ 717 | from: 'file.txt', 718 | to: 'newdirectory.ext/' 719 | }] 720 | }) 721 | .then(done) 722 | .catch(done); 723 | }); 724 | 725 | it('can move a file to a new file with a different name', (done) => { 726 | runEmit({ 727 | expectedAssetKeys: [ 728 | 'newname.txt' 729 | ], 730 | patterns: [{ 731 | from: 'file.txt', 732 | to: 'newname.txt' 733 | }] 734 | }) 735 | .then(done) 736 | .catch(done); 737 | }); 738 | 739 | it('can move a file to a new file with no extension', (done) => { 740 | runEmit({ 741 | expectedAssetKeys: [ 742 | 'newname' 743 | ], 744 | patterns: [{ 745 | from: 'file.txt', 746 | to: 'newname', 747 | toType: 'file' 748 | }] 749 | }) 750 | .then(done) 751 | .catch(done); 752 | }); 753 | 754 | it('can move a file without an extension to a file using a template', (done) => { 755 | runEmit({ 756 | expectedAssetKeys: [ 757 | 'noextension.newext' 758 | ], 759 | patterns: [{ 760 | from: 'noextension', 761 | to: '[name][ext].newext' 762 | }] 763 | }) 764 | .then(done) 765 | .catch(done); 766 | }); 767 | 768 | it('can move a file with a ".bin" extension using a template', (done) => { 769 | runEmit({ 770 | expectedAssetKeys: [ 771 | 'binextension.bin' 772 | ], 773 | patterns: [{ 774 | from: 'binextension.bin', 775 | to: '[name].[ext]' 776 | }] 777 | }) 778 | .then(done) 779 | .catch(done); 780 | }); 781 | 782 | it('can move a nested file to the root directory', (done) => { 783 | runEmit({ 784 | expectedAssetKeys: [ 785 | 'directoryfile.txt' 786 | ], 787 | patterns: [{ 788 | from: 'directory/directoryfile.txt' 789 | }] 790 | }) 791 | .then(done) 792 | .catch(done); 793 | }); 794 | 795 | it('can use an absolute path to move a nested file to the root directory', (done) => { 796 | const absolutePath = path.resolve(HELPER_DIR, 'directory', 'directoryfile.txt'); 797 | 798 | runEmit({ 799 | expectedAssetKeys: [ 800 | 'directoryfile.txt' 801 | ], 802 | patterns: [{ 803 | from: absolutePath 804 | }] 805 | }) 806 | .then(done) 807 | .catch(done); 808 | }); 809 | 810 | it('can move a nested file to a new directory', (done) => { 811 | runEmit({ 812 | expectedAssetKeys: [ 813 | 'newdirectory/directoryfile.txt' 814 | ], 815 | patterns: [{ 816 | from: 'directory/directoryfile.txt', 817 | to: 'newdirectory' 818 | }] 819 | }) 820 | .then(done) 821 | .catch(done); 822 | }); 823 | 824 | it('can use an absolute path to move a nested file to a new directory', (done) => { 825 | const absolutePath = path.resolve(HELPER_DIR, 'directory', 'directoryfile.txt'); 826 | 827 | runEmit({ 828 | expectedAssetKeys: [ 829 | 'newdirectory/directoryfile.txt' 830 | ], 831 | patterns: [{ 832 | from: absolutePath, 833 | to: 'newdirectory' 834 | }] 835 | }) 836 | .then(done) 837 | .catch(done); 838 | }); 839 | 840 | it('won\'t overwrite a file already in the compilation', (done) => { 841 | runForce({ 842 | existingAsset: 'file.txt', 843 | expectedAssetContent: { 844 | 'file.txt': 'existing' 845 | }, 846 | patterns: [{ 847 | from: 'file.txt' 848 | }] 849 | }) 850 | .then(done) 851 | .catch(done); 852 | }); 853 | 854 | it('can force overwrite of a file already in the compilation', (done) => { 855 | runForce({ 856 | existingAsset: 'file.txt', 857 | expectedAssetContent: { 858 | 'file.txt': 'new' 859 | }, 860 | patterns: [{ 861 | force: true, 862 | from: 'file.txt' 863 | }] 864 | }) 865 | .then(done) 866 | .catch(done); 867 | }); 868 | 869 | it('adds the file to the watch list', (done) => { 870 | run({ 871 | patterns: [{ 872 | from: 'file.txt' 873 | }] 874 | }) 875 | .then((compilation) => { 876 | const absFrom = path.join(HELPER_DIR, 'file.txt'); 877 | 878 | expect(compilation.fileDependencies).to.have.members([absFrom]); 879 | }) 880 | .then(done) 881 | .catch(done); 882 | }); 883 | 884 | it('only include files that have changed', (done) => { 885 | runChange({ 886 | expectedAssetKeys: [ 887 | 'tempfile1.txt' 888 | ], 889 | newFileLoc1: path.join(HELPER_DIR, 'tempfile1.txt'), 890 | newFileLoc2: path.join(HELPER_DIR, 'tempfile2.txt'), 891 | patterns: [{ 892 | from: 'tempfile1.txt' 893 | }, { 894 | from: 'tempfile2.txt' 895 | }] 896 | }) 897 | .then(done) 898 | .catch(done); 899 | }); 900 | 901 | it('ignores files in pattern', (done) => { 902 | runEmit({ 903 | expectedAssetKeys: [ 904 | '[!]/hello.txt', 905 | 'binextension.bin', 906 | 'directory/directoryfile.txt', 907 | 'directory/nested/nestedfile.txt', 908 | '[special?directory]/directoryfile.txt', 909 | '[special?directory]/(special-*file).txt', 910 | '[special?directory]/nested/nestedfile.txt', 911 | 'noextension' 912 | ], 913 | patterns: [{ 914 | from: '**/*', 915 | ignore: [ 916 | 'file.*' 917 | ] 918 | }] 919 | }) 920 | .then(done) 921 | .catch(done); 922 | }); 923 | 924 | it('allows pattern to contain name, hash or ext', (done) => { 925 | runEmit({ 926 | expectedAssetKeys: [ 927 | 'directory/directoryfile-22af64.txt' 928 | ], 929 | patterns: [{ 930 | from: 'directory/directoryfile.txt', 931 | to: 'directory/[name]-[hash:6].[ext]' 932 | }] 933 | }) 934 | .then(done) 935 | .catch(done); 936 | }); 937 | 938 | it('transform with promise', (done) => { 939 | runEmit({ 940 | expectedAssetKeys: [ 941 | 'file.txt' 942 | ], 943 | expectedAssetContent: { 944 | 'file.txt': 'newchanged!' 945 | }, 946 | patterns: [{ 947 | from: 'file.txt', 948 | transform: function(content) { 949 | return new Promise((resolve) => { 950 | resolve(content + 'changed!'); 951 | }); 952 | } 953 | }] 954 | }) 955 | .then(done) 956 | .catch(done); 957 | }); 958 | 959 | it('same file to multiple targets', (done) => { 960 | runEmit({ 961 | expectedAssetKeys: [ 962 | 'first/file.txt', 963 | 'second/file.txt' 964 | ], 965 | patterns: [{ 966 | from: 'file.txt', 967 | to: 'first/file.txt' 968 | }, { 969 | from: 'file.txt', 970 | to: 'second/file.txt' 971 | }] 972 | }) 973 | .then(done) 974 | .catch(done); 975 | }); 976 | }); 977 | 978 | describe('with directory in from', () => { 979 | it('can move a directory\'s contents to the root directory', (done) => { 980 | runEmit({ 981 | expectedAssetKeys: [ 982 | '.dottedfile', 983 | 'directoryfile.txt', 984 | 'nested/nestedfile.txt' 985 | ], 986 | patterns: [{ 987 | from: 'directory' 988 | }] 989 | }) 990 | .then(done) 991 | .catch(done); 992 | }); 993 | 994 | it('can move a directory\'s contents to the root directory using from with special characters', (done) => { 995 | runEmit({ 996 | expectedAssetKeys: [ 997 | 'directoryfile.txt', 998 | '(special-*file).txt', 999 | 'nested/nestedfile.txt' 1000 | ], 1001 | patterns: [{ 1002 | from: (path.sep === '/' ? '[special?directory]' : '[specialdirectory]') 1003 | }] 1004 | }) 1005 | .then(done) 1006 | .catch(done); 1007 | }); 1008 | 1009 | it('can move a directory\'s contents to the root directory using context with special characters', (done) => { 1010 | runEmit({ 1011 | expectedAssetKeys: [ 1012 | 'directoryfile.txt', 1013 | '(special-*file).txt', 1014 | 'nested/nestedfile.txt' 1015 | ], 1016 | patterns: [{ 1017 | from: '.', 1018 | context: '[special?directory]' 1019 | }] 1020 | }) 1021 | .then(done) 1022 | .catch(done); 1023 | }); 1024 | 1025 | it('warns when directory not found', (done) => { 1026 | runEmit({ 1027 | expectedAssetKeys: [], 1028 | expectedErrors: [ 1029 | `[copy-webpack-plugin] unable to locate 'nonexistent' at '${HELPER_DIR}${path.sep}nonexistent'` 1030 | ], 1031 | patterns: [{ 1032 | from: 'nonexistent' 1033 | }] 1034 | }) 1035 | .then(done) 1036 | .catch(done); 1037 | }); 1038 | 1039 | it('can use an absolute path to move a directory\'s contents to the root directory', (done) => { 1040 | const absolutePath = path.resolve(HELPER_DIR, 'directory'); 1041 | 1042 | runEmit({ 1043 | expectedAssetKeys: [ 1044 | '.dottedfile', 1045 | 'directoryfile.txt', 1046 | 'nested/nestedfile.txt' 1047 | ], 1048 | patterns: [{ 1049 | from: absolutePath 1050 | }] 1051 | }) 1052 | .then(done) 1053 | .catch(done); 1054 | }); 1055 | 1056 | it('can move a directory\'s contents to a new directory', (done) => { 1057 | runEmit({ 1058 | expectedAssetKeys: [ 1059 | 'newdirectory/.dottedfile', 1060 | 'newdirectory/directoryfile.txt', 1061 | 'newdirectory/nested/nestedfile.txt' 1062 | ], 1063 | patterns: [{ 1064 | from: 'directory', 1065 | to: 'newdirectory' 1066 | }] 1067 | }) 1068 | .then(done) 1069 | .catch(done); 1070 | }); 1071 | 1072 | it('can move a directory\'s contents to a new directory using a pattern context', (done) => { 1073 | runEmit({ 1074 | expectedAssetKeys: [ 1075 | 'newdirectory/nestedfile.txt' 1076 | ], 1077 | patterns: [{ 1078 | context: 'directory', 1079 | from: 'nested', 1080 | to: 'newdirectory' 1081 | }] 1082 | }) 1083 | .then(done) 1084 | .catch(done); 1085 | }); 1086 | 1087 | it('can flatten a directory\'s contents to a new directory', (done) => { 1088 | runEmit({ 1089 | expectedAssetKeys: [ 1090 | 'newdirectory/.dottedfile', 1091 | 'newdirectory/directoryfile.txt', 1092 | 'newdirectory/nestedfile.txt' 1093 | ], 1094 | patterns: [{ 1095 | flatten: true, 1096 | from: 'directory', 1097 | to: 'newdirectory' 1098 | }] 1099 | }) 1100 | .then(done) 1101 | .catch(done); 1102 | }); 1103 | 1104 | it('can move a directory\'s contents to a new directory using an absolute to', (done) => { 1105 | runEmit({ 1106 | expectedAssetKeys: [ 1107 | '../tempdir/.dottedfile', 1108 | '../tempdir/directoryfile.txt', 1109 | '../tempdir/nested/nestedfile.txt' 1110 | ], 1111 | patterns: [{ 1112 | from: 'directory', 1113 | to: TEMP_DIR 1114 | }] 1115 | }) 1116 | .then(done) 1117 | .catch(done); 1118 | }); 1119 | 1120 | it('can move a nested directory\'s contents to the root directory', (done) => { 1121 | runEmit({ 1122 | expectedAssetKeys: [ 1123 | 'nestedfile.txt' 1124 | ], 1125 | patterns: [{ 1126 | from: 'directory/nested' 1127 | }] 1128 | }) 1129 | .then(done) 1130 | .catch(done); 1131 | }); 1132 | 1133 | it('can move a nested directory\'s contents to a new directory', (done) => { 1134 | runEmit({ 1135 | expectedAssetKeys: [ 1136 | 'newdirectory/nestedfile.txt' 1137 | ], 1138 | patterns: [{ 1139 | from: 'directory/nested', 1140 | to: 'newdirectory' 1141 | }] 1142 | }) 1143 | .then(done) 1144 | .catch(done); 1145 | }); 1146 | 1147 | it('can use an absolute path to move a nested directory\'s contents to a new directory', (done) => { 1148 | const absolutePath = path.resolve(HELPER_DIR, 'directory', 'nested'); 1149 | 1150 | runEmit({ 1151 | expectedAssetKeys: [ 1152 | 'newdirectory/nestedfile.txt' 1153 | ], 1154 | patterns: [{ 1155 | from: absolutePath, 1156 | to: 'newdirectory' 1157 | }] 1158 | }) 1159 | .then(done) 1160 | .catch(done); 1161 | }); 1162 | 1163 | it('won\'t overwrite a file already in the compilation', (done) => { 1164 | runForce({ 1165 | existingAsset: 'directoryfile.txt', 1166 | expectedAssetContent: { 1167 | 'directoryfile.txt': 'existing' 1168 | }, 1169 | patterns: [{ 1170 | from: 'directory' 1171 | }] 1172 | }) 1173 | .then(done) 1174 | .catch(done); 1175 | }); 1176 | 1177 | it('can force overwrite of a file already in the compilation', (done) => { 1178 | runForce({ 1179 | existingAsset: 'directoryfile.txt', 1180 | expectedAssetContent: { 1181 | 'directoryfile.txt': 'new' 1182 | }, 1183 | patterns: [{ 1184 | force: true, 1185 | from: 'directory' 1186 | }] 1187 | }) 1188 | .then(done) 1189 | .catch(done); 1190 | }); 1191 | 1192 | it('adds the directory to the watch list', (done) => { 1193 | run({ 1194 | patterns: [{ 1195 | from: 'directory' 1196 | }] 1197 | }) 1198 | .then((compilation) => { 1199 | const absFrom = path.join(HELPER_DIR, 'directory'); 1200 | 1201 | expect(compilation.contextDependencies).to.have.members([absFrom]); 1202 | }) 1203 | .then(done) 1204 | .catch(done); 1205 | }); 1206 | 1207 | it('only include files that have changed', (done) => { 1208 | runChange({ 1209 | expectedAssetKeys: [ 1210 | 'tempfile1.txt' 1211 | ], 1212 | newFileLoc1: path.join(HELPER_DIR, 'directory', 'tempfile1.txt'), 1213 | newFileLoc2: path.join(HELPER_DIR, 'directory', 'tempfile2.txt'), 1214 | patterns: [{ 1215 | from: 'directory' 1216 | }] 1217 | }) 1218 | .then(done) 1219 | .catch(done); 1220 | }); 1221 | 1222 | it('include all files if copyUnmodified is true', (done) => { 1223 | runChange({ 1224 | expectedAssetKeys: [ 1225 | '.dottedfile', 1226 | 'directoryfile.txt', 1227 | 'nested/nestedfile.txt', 1228 | 'tempfile1.txt', 1229 | 'tempfile2.txt' 1230 | ], 1231 | newFileLoc1: path.join(HELPER_DIR, 'directory', 'tempfile1.txt'), 1232 | newFileLoc2: path.join(HELPER_DIR, 'directory', 'tempfile2.txt'), 1233 | options: { 1234 | copyUnmodified: true 1235 | }, 1236 | patterns: [{ 1237 | from: 'directory' 1238 | }] 1239 | }) 1240 | .then(done) 1241 | .catch(done); 1242 | }); 1243 | 1244 | it('can move multiple files to a non-root directory with name, hash and ext', (done) => { 1245 | runEmit({ 1246 | expectedAssetKeys: [ 1247 | 'nested/.dottedfile-79d39f', 1248 | 'nested/directoryfile-22af64.txt', 1249 | 'nested/nested/nestedfile-d41d8c.txt' 1250 | ], 1251 | patterns: [{ 1252 | from: 'directory', 1253 | to: 'nested/[path][name]-[hash:6].[ext]' 1254 | }] 1255 | }) 1256 | .then(done) 1257 | .catch(done); 1258 | }); 1259 | }); 1260 | 1261 | describe('with simple string patterns', () => { 1262 | it('can move multiple files', (done) => { 1263 | runEmit({ 1264 | expectedAssetKeys: [ 1265 | 'binextension.bin', 1266 | 'file.txt', 1267 | 'noextension' 1268 | ], 1269 | patterns: [ 1270 | 'binextension.bin', 1271 | 'file.txt', 1272 | 'noextension' 1273 | ] 1274 | }) 1275 | .then(done) 1276 | .catch(done); 1277 | }); 1278 | }); 1279 | 1280 | describe('options', () => { 1281 | describe('ignore', () => { 1282 | it('ignores files when from is a file', (done) => { 1283 | runEmit({ 1284 | expectedAssetKeys: [ 1285 | 'directoryfile.txt' 1286 | ], 1287 | options: { 1288 | ignore: [ 1289 | 'file.*' 1290 | ] 1291 | }, 1292 | patterns: [{ 1293 | from: 'file.txt' 1294 | }, { 1295 | from: 'directory/directoryfile.txt' 1296 | }] 1297 | }) 1298 | .then(done) 1299 | .catch(done); 1300 | }); 1301 | 1302 | it('ignores files when from is a directory', (done) => { 1303 | runEmit({ 1304 | expectedAssetKeys: [ 1305 | '.dottedfile', 1306 | 'directoryfile.txt' 1307 | ], 1308 | options: { 1309 | ignore: [ 1310 | '*/nestedfile.*' 1311 | ] 1312 | }, 1313 | patterns: [{ 1314 | from: 'directory' 1315 | }] 1316 | }) 1317 | .then(done) 1318 | .catch(done); 1319 | }); 1320 | 1321 | it('ignores files with a certain extension', (done) => { 1322 | runEmit({ 1323 | expectedAssetKeys: [ 1324 | '.dottedfile' 1325 | ], 1326 | options: { 1327 | ignore: [ 1328 | '*.txt' 1329 | ] 1330 | }, 1331 | patterns: [{ 1332 | from: 'directory' 1333 | }] 1334 | }) 1335 | .then(done) 1336 | .catch(done); 1337 | }); 1338 | 1339 | it('ignores files that start with a dot', (done) => { 1340 | runEmit({ 1341 | expectedAssetKeys: [ 1342 | '[!]/hello.txt', 1343 | 'binextension.bin', 1344 | 'file.txt', 1345 | 'file.txt.gz', 1346 | 'directory/directoryfile.txt', 1347 | 'directory/nested/nestedfile.txt', 1348 | '[special?directory]/directoryfile.txt', 1349 | '[special?directory]/(special-*file).txt', 1350 | '[special?directory]/nested/nestedfile.txt', 1351 | 'noextension' 1352 | ], 1353 | options: { 1354 | ignore: [ 1355 | '.dottedfile' 1356 | ] 1357 | }, 1358 | patterns: [{ 1359 | from: '.' 1360 | }] 1361 | }) 1362 | .then(done) 1363 | .catch(done); 1364 | }); 1365 | 1366 | it('ignores all files except those with dots', (done) => { 1367 | runEmit({ 1368 | expectedAssetKeys: [ 1369 | 'directory/.dottedfile' 1370 | ], 1371 | options: { 1372 | ignore: [{ 1373 | dot: false, 1374 | glob: '**/*' 1375 | }] 1376 | }, 1377 | patterns: [{ 1378 | from: '.' 1379 | }] 1380 | }) 1381 | .then(done) 1382 | .catch(done); 1383 | }); 1384 | 1385 | it('ignores all files even if they start with a dot', (done) => { 1386 | runEmit({ 1387 | expectedAssetKeys: [], 1388 | options: { 1389 | ignore: ['**/*'] 1390 | }, 1391 | patterns: [{ 1392 | from: '.' 1393 | }] 1394 | 1395 | }) 1396 | .then(done) 1397 | .catch(done); 1398 | }); 1399 | 1400 | it('ignores nested directory', (done) => { 1401 | runEmit({ 1402 | expectedAssetKeys: [ 1403 | '[!]/hello.txt', 1404 | 'binextension.bin', 1405 | 'file.txt', 1406 | 'file.txt.gz', 1407 | 'noextension' 1408 | ], 1409 | options: { 1410 | ignore: ['directory/**/*', `[[]special${process.platform === 'win32' ? '' : '[?]'}directory]/**/*`] 1411 | }, 1412 | patterns: [{ 1413 | from: '.' 1414 | }] 1415 | 1416 | }) 1417 | .then(done) 1418 | .catch(done); 1419 | }); 1420 | 1421 | if (path.sep === '/') { 1422 | it('ignores nested directory(can use "\\" to escape if path.sep is "/")', (done) => { 1423 | runEmit({ 1424 | expectedAssetKeys: [ 1425 | '[!]/hello.txt', 1426 | 'binextension.bin', 1427 | 'file.txt', 1428 | 'file.txt.gz', 1429 | 'noextension' 1430 | ], 1431 | options: { 1432 | ignore: ['directory/**/*', '\\[special\\?directory\\]/**/*'] 1433 | }, 1434 | patterns: [{ 1435 | from: '.' 1436 | }] 1437 | 1438 | }) 1439 | .then(done) 1440 | .catch(done); 1441 | }); 1442 | } 1443 | 1444 | it('ignores nested directory (glob)', (done) => { 1445 | runEmit({ 1446 | expectedAssetKeys: [ 1447 | '.dottedfile', 1448 | 'directoryfile.txt' 1449 | ], 1450 | options: { 1451 | ignore: ['nested/**/*'] 1452 | }, 1453 | patterns: [{ 1454 | from: 'directory' 1455 | }] 1456 | }) 1457 | .then(done) 1458 | .catch(done); 1459 | }); 1460 | }); 1461 | 1462 | describe('context', () => { 1463 | it('overrides webpack config context with absolute path', (done) => { 1464 | runEmit({ 1465 | expectedAssetKeys: [ 1466 | 'newdirectory/nestedfile.txt' 1467 | ], 1468 | options: { 1469 | context: path.resolve(HELPER_DIR, 'directory') 1470 | }, 1471 | patterns: [{ 1472 | from: 'nested', 1473 | to: 'newdirectory' 1474 | }] 1475 | }) 1476 | .then(done) 1477 | .catch(done); 1478 | }); 1479 | 1480 | it('overrides webpack config context with relative path', (done) => { 1481 | runEmit({ 1482 | expectedAssetKeys: [ 1483 | 'newdirectory/nestedfile.txt' 1484 | ], 1485 | options: { 1486 | context: 'directory' 1487 | }, 1488 | patterns: [{ 1489 | from: 'nested', 1490 | to: 'newdirectory' 1491 | }] 1492 | }) 1493 | .then(done) 1494 | .catch(done); 1495 | }); 1496 | 1497 | it('is overridden by pattern context', (done) => { 1498 | runEmit({ 1499 | expectedAssetKeys: [ 1500 | 'newdirectory/nestedfile.txt' 1501 | ], 1502 | options: { 1503 | context: 'directory' 1504 | }, 1505 | patterns: [{ 1506 | context: 'nested', 1507 | from: '.', 1508 | to: 'newdirectory' 1509 | }] 1510 | }) 1511 | .then(done) 1512 | .catch(done); 1513 | }); 1514 | }); 1515 | 1516 | describe('cache', () => { 1517 | const cacheDir = findCacheDir({ name: 'copy-webpack-plugin' }); 1518 | 1519 | beforeEach(() => cacache.rm.all(cacheDir)); 1520 | 1521 | it('file should be cached', (done) => { 1522 | const newContent = 'newchanged!'; 1523 | const from = 'file.txt'; 1524 | 1525 | runEmit({ 1526 | expectedAssetKeys: [ 1527 | 'file.txt' 1528 | ], 1529 | expectedAssetContent: { 1530 | 'file.txt': newContent 1531 | }, 1532 | patterns: [{ 1533 | from: from, 1534 | cache: true, 1535 | transform: function(content) { 1536 | return new Promise((resolve) => { 1537 | resolve(content + 'changed!'); 1538 | }); 1539 | } 1540 | }] 1541 | }) 1542 | .then(() => { 1543 | return cacache 1544 | .ls(cacheDir) 1545 | .then((cacheEntries) => { 1546 | const cacheKeys = Object.keys(cacheEntries); 1547 | 1548 | expect(cacheKeys).to.have.lengthOf(1); 1549 | 1550 | cacheKeys.forEach((cacheKey) => { 1551 | const cacheEntry = new Function(`'use strict'\nreturn ${cacheKey}`)(); 1552 | 1553 | expect(cacheEntry.pattern.from).to.equal(from); 1554 | }); 1555 | }); 1556 | }) 1557 | .then(done) 1558 | .catch(done); 1559 | }); 1560 | 1561 | it('files in directory should be cached', (done) => { 1562 | const from = 'directory'; 1563 | 1564 | runEmit({ 1565 | expectedAssetKeys: [ 1566 | '.dottedfile', 1567 | 'directoryfile.txt', 1568 | 'nested/nestedfile.txt' 1569 | ], 1570 | expectedAssetContent: { 1571 | '.dottedfile': 'dottedfile contents\nchanged!', 1572 | 'directoryfile.txt': 'newchanged!', 1573 | 'nested/nestedfile.txt': 'changed!' 1574 | }, 1575 | patterns: [{ 1576 | from: from, 1577 | cache: true, 1578 | transform: function(content) { 1579 | return new Promise((resolve) => { 1580 | resolve(content + 'changed!'); 1581 | }); 1582 | } 1583 | }] 1584 | }) 1585 | .then(() => { 1586 | return cacache 1587 | .ls(cacheDir) 1588 | .then((cacheEntries) => { 1589 | const cacheKeys = Object.keys(cacheEntries); 1590 | 1591 | expect(cacheKeys).to.have.lengthOf(3); 1592 | 1593 | cacheKeys.forEach((cacheKey) => { 1594 | const cacheEntry = new Function(`'use strict'\nreturn ${cacheKey}`)(); 1595 | 1596 | expect(cacheEntry.pattern.from).to.equal(from); 1597 | }); 1598 | }); 1599 | }) 1600 | .then(done) 1601 | .catch(done); 1602 | }); 1603 | 1604 | it('glob should be cached', (done) => { 1605 | const from = '*.txt'; 1606 | 1607 | runEmit({ 1608 | expectedAssetKeys: [ 1609 | 'file.txt' 1610 | ], 1611 | expectedAssetContent: { 1612 | 'file.txt': 'newchanged!' 1613 | }, 1614 | patterns: [{ 1615 | from: from, 1616 | cache: true, 1617 | transform: function(content) { 1618 | return new Promise((resolve) => { 1619 | resolve(content + 'changed!'); 1620 | }); 1621 | } 1622 | }] 1623 | }) 1624 | .then(() => { 1625 | return cacache 1626 | .ls(cacheDir) 1627 | .then((cacheEntries) => { 1628 | const cacheKeys = Object.keys(cacheEntries); 1629 | 1630 | expect(cacheKeys).to.have.lengthOf(1); 1631 | 1632 | cacheKeys.forEach((cacheKey) => { 1633 | const cacheEntry = new Function(`'use strict'\nreturn ${cacheKey}`)(); 1634 | 1635 | expect(cacheEntry.pattern.from).to.equal(from); 1636 | }); 1637 | }); 1638 | }) 1639 | .then(done) 1640 | .catch(done); 1641 | }); 1642 | 1643 | it('file should be cached with custom cache key', (done) => { 1644 | const newContent = 'newchanged!'; 1645 | const from = 'file.txt'; 1646 | 1647 | runEmit({ 1648 | expectedAssetKeys: [ 1649 | 'file.txt' 1650 | ], 1651 | expectedAssetContent: { 1652 | 'file.txt': newContent 1653 | }, 1654 | patterns: [{ 1655 | from: from, 1656 | cache: { 1657 | key: 'foobar' 1658 | }, 1659 | transform: function(content) { 1660 | return new Promise((resolve) => { 1661 | resolve(content + 'changed!'); 1662 | }); 1663 | } 1664 | }] 1665 | }) 1666 | .then(() => { 1667 | return cacache 1668 | .ls(cacheDir) 1669 | .then((cacheEntries) => { 1670 | const cacheKeys = Object.keys(cacheEntries); 1671 | 1672 | expect(cacheKeys).to.have.lengthOf(1); 1673 | 1674 | cacheKeys.forEach((cacheKey) => { 1675 | expect(cacheKey).to.equal('foobar'); 1676 | }); 1677 | }); 1678 | }) 1679 | .then(done) 1680 | .catch(done); 1681 | }); 1682 | 1683 | it('binary file should be cached', (done) => { 1684 | const from = 'file.txt.gz'; 1685 | const content = fs.readFileSync(path.join(HELPER_DIR, from)); 1686 | const expectedNewContent = zlib.gzipSync('newchanged!'); 1687 | 1688 | expect(isGzip(content)).to.equal(true); 1689 | expect(isGzip(expectedNewContent)).to.equal(true); 1690 | 1691 | runEmit({ 1692 | expectedAssetKeys: [ 1693 | 'file.txt.gz' 1694 | ], 1695 | expectedAssetContent: { 1696 | 'file.txt.gz': expectedNewContent 1697 | }, 1698 | patterns: [{ 1699 | from: from, 1700 | cache: true, 1701 | transform: function(content) { 1702 | expect(isGzip(content)).to.equal(true); 1703 | 1704 | return new Promise((resolve) => { 1705 | zlib.unzip(content, (error, content) => { 1706 | if (error) { 1707 | throw error; 1708 | } 1709 | 1710 | const newContent = new Buffer(content + 'changed!'); 1711 | 1712 | zlib.gzip(newContent, (error, compressedData) => { 1713 | if (error) { 1714 | throw error; 1715 | } 1716 | 1717 | expect(isGzip(compressedData)).to.equal(true); 1718 | 1719 | return resolve(compressedData); 1720 | }); 1721 | }); 1722 | }); 1723 | } 1724 | }] 1725 | }) 1726 | .then(() => { 1727 | return cacache 1728 | .ls(cacheDir) 1729 | .then((cacheEntries) => { 1730 | const cacheKeys = Object.keys(cacheEntries); 1731 | 1732 | expect(cacheKeys).to.have.lengthOf(1); 1733 | 1734 | cacheKeys.forEach((cacheKey) => { 1735 | const cacheEntry = new Function(`'use strict'\nreturn ${cacheKey}`)(); 1736 | 1737 | expect(cacheEntry.pattern.from).to.equal(from); 1738 | }); 1739 | }); 1740 | }) 1741 | .then(done) 1742 | .catch(done); 1743 | }); 1744 | 1745 | after(() => cacache.rm.all(cacheDir)); 1746 | }); 1747 | }); 1748 | }); 1749 | -------------------------------------------------------------------------------- /tests/utils/removeIllegalCharacterForWindows.js: -------------------------------------------------------------------------------- 1 | module.exports = function (string) { 2 | return process.platform !== 'win32' ? string : string.replace(/[*?"<>|]/g, ''); 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /tests/weappHelpers/components/base/Dep/index.js: -------------------------------------------------------------------------------- 1 | export default function Dep () {} -------------------------------------------------------------------------------- /tests/weappHelpers/components/base/Page.js: -------------------------------------------------------------------------------- 1 | import Dep from './Dep'; 2 | 3 | export default function Page () { 4 | console.log(Dep); 5 | } -------------------------------------------------------------------------------- /tests/weappHelpers/components/button/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessejyang/import-weapp-component/3e70cd33b6097d155893f7556435a42345c51de8/tests/weappHelpers/components/button/index.js -------------------------------------------------------------------------------- /tests/weappHelpers/components/button/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /tests/weappHelpers/components/button/index.wxml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessejyang/import-weapp-component/3e70cd33b6097d155893f7556435a42345c51de8/tests/weappHelpers/components/button/index.wxml -------------------------------------------------------------------------------- /tests/weappHelpers/components/comp1/index.js: -------------------------------------------------------------------------------- 1 | // import Page from '../base/Page'; 2 | const Page = require('../base/Page'); 3 | 4 | Page(); -------------------------------------------------------------------------------- /tests/weappHelpers/components/comp1/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true 3 | } -------------------------------------------------------------------------------- /tests/weappHelpers/components/comp1/index.wxml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessejyang/import-weapp-component/3e70cd33b6097d155893f7556435a42345c51de8/tests/weappHelpers/components/comp1/index.wxml -------------------------------------------------------------------------------- /tests/weappHelpers/components/panel/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessejyang/import-weapp-component/3e70cd33b6097d155893f7556435a42345c51de8/tests/weappHelpers/components/panel/index.js -------------------------------------------------------------------------------- /tests/weappHelpers/components/panel/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "button": "../button/index" 4 | } 5 | } -------------------------------------------------------------------------------- /tests/weappHelpers/components/panel/index.wxml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessejyang/import-weapp-component/3e70cd33b6097d155893f7556435a42345c51de8/tests/weappHelpers/components/panel/index.wxml -------------------------------------------------------------------------------- /tests/weappHelpers/entries.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | path: 'pages/normal', 4 | config: { 5 | usingComponents: { 6 | 'button': '/components/button/index' 7 | } 8 | } 9 | }, 10 | { 11 | path: 'pages/self', 12 | config: { 13 | usingComponents: { 14 | 'panel': '../components/panel/index' 15 | } 16 | } 17 | } 18 | ]; -------------------------------------------------------------------------------- /tests/weappHelpers/pages/native/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessejyang/import-weapp-component/3e70cd33b6097d155893f7556435a42345c51de8/tests/weappHelpers/pages/native/index.js -------------------------------------------------------------------------------- /tests/weappHelpers/pages/native/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /tests/weappHelpers/pages/native/index.wxml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jessejyang/import-weapp-component/3e70cd33b6097d155893f7556435a42345c51de8/tests/weappHelpers/pages/native/index.wxml -------------------------------------------------------------------------------- /tests/weappHelpers/pages/normal/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "button": "/components/panel/index" 4 | } 5 | } -------------------------------------------------------------------------------- /tests/weappHelpers/pages/notexist/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /tests/weappHelpers/pages/self/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "button": "../../components/button/index" 4 | } 5 | } --------------------------------------------------------------------------------