├── .gitignore ├── .prettierignore ├── .prettierrc.yaml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README-zh_CN.md ├── README.md ├── examples ├── README.md ├── html-webpack-plugin │ ├── app.js │ ├── lib.js │ ├── webpack.config.js │ └── webpack.dll.config.js ├── multiple-entries │ ├── app.js │ ├── lib1.js │ ├── lib2.js │ ├── webpack.config.js │ └── webpack.dll.config.js ├── package.json ├── simple │ ├── app.js │ ├── lib.js │ ├── webpack.config.js │ └── webpack.dll.config.js └── yarn.lock ├── index.d.ts ├── jest.config.js ├── package.json ├── src ├── BundleController.ts ├── CacheController.ts ├── index.ts └── utils │ └── packageDependency.ts ├── tests ├── BundleController.test.ts ├── CacheController.test.ts └── utils │ └── packageDependency.test.ts ├── tsconfig.json ├── tslint.json ├── why-use-dll-link.md └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .dll-link-plugin/ 4 | examples/build 5 | .npmrc -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .dll-link-plugin/ -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | # .prettierrc 2 | tabWidth: 4 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - lts/* 4 | sudo: false 5 | 6 | os: 7 | - osx 8 | - linux 9 | 10 | script: 11 | - npm test --silent -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [3.2.1] - 2017-10-31 4 | 5 | ### Bug Fixes 6 | 7 | * Fix invalid path separator in Windows(This causes DllLinkPlugin unable to run in Windows.) 8 | * Use path.join to join path 9 | 10 | ## [3.2.0] - 2017-10-30 11 | 12 | ### Bug Fixes 13 | 14 | * Enable add css to HtmlWebpackPlugin 15 | 16 | ### Features 17 | 18 | * Support HtmlWebPackPlugin 4.x 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 clinyong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | export PATH := $(shell pwd)/node_modules/.bin:$(PATH) 3 | 4 | watch: 5 | tsc -w 6 | 7 | build: 8 | tsc 9 | 10 | publish:build 11 | npm publish -------------------------------------------------------------------------------- /README-zh_CN.md: -------------------------------------------------------------------------------- 1 | ## Dll Link Plugin 2 | 3 | 用于简化生成 webpack DLL 的插件。基于 [DllReferencePlugin](https://webpack.js.org/plugins/dll-plugin/#dllreferenceplugin)。可以看下这篇[博客](http://www.jianshu.com/p/a5b3c2284bb6)的介绍。 4 | 5 | ### 安装 6 | 7 | ``` 8 | $ yarn add dll-link-webpack-plugin -D 9 | ``` 10 | 11 | ### 基础用法 12 | 13 | 在 `webpack.config.js` 这个配置文件里面,用 `DllLinkPlugin` 替换掉 `DllReferencePlugin`。 14 | 15 | ```js 16 | var DllLinkPlugin = require("dll-link-webpack-plugin"); 17 | 18 | module.exports = { 19 | // ... 20 | plugins: [ 21 | new DllLinkPlugin({ 22 | config: require("webpack.dll.config.js") 23 | }) 24 | ] 25 | }; 26 | ``` 27 | 28 | 然后运行 29 | 30 | ``` 31 | $ webpack --config webpack.config.js 32 | ``` 33 | 34 | 这个命令会自动生成 DLL 文件。关于插件的更多用法,可以查看项目自带的[例子](https://github.com/clinyong/dll-link-webpack-plugin/tree/master/examples)。 35 | 36 | ### 配置 37 | 38 | * `htmlMode`: `true` | `false` 如果你用了 [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin),生成出来的 DLL 文件会被自动引入 html 文件中。(默认值为 `false`) 39 | * `assetsMode`: `true` | `false` 把 DLL 文件输出为 webpack 的 assets 文件。(默认值为 `false`) 40 | * `appendVersion`: `true` | `false` 给每个 webpack 生成出的 entry 文件打上一个版本号。(默认值为 `false`) 41 | 42 | 上面配置项的例子: 43 | 44 | ```js 45 | module.exports = { 46 | // ... 47 | plugins: [ 48 | new DllLinkPlugin({ 49 | config: require("webpack.dll.config.js"), 50 | appendVersion: true, 51 | assetsMode: true, 52 | htmlMode: true 53 | }) 54 | ] 55 | }; 56 | ``` 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Dll Link Plugin 2 | 3 | A webpack plugin that simplifies creation of webpack DLL file. It is based on [DllReferencePlugin](https://webpack.js.org/plugins/dll-plugin/#dllreferenceplugin). And you can see the difference [here](https://github.com/clinyong/dll-link-webpack-plugin/blob/master/why-use-dll-link.md). 4 | 5 | [中文 README](https://github.com/clinyong/dll-link-webpack-plugin/blob/master/README-zh_CN.md) 6 | 7 | ### Install 8 | 9 | ``` 10 | $ yarn add dll-link-webpack-plugin -D 11 | ``` 12 | 13 | ### Basic Usage 14 | 15 | Replace `DllReferencePlugin` with `DllLinkPlugin` in your `webpack.config.js` 16 | 17 | ```js 18 | var DllLinkPlugin = require("dll-link-webpack-plugin"); 19 | 20 | module.exports = { 21 | // ... 22 | plugins: [ 23 | new DllLinkPlugin({ 24 | config: require("webpack.dll.config.js") 25 | }) 26 | ] 27 | }; 28 | ``` 29 | 30 | And directly run 31 | 32 | ``` 33 | $ webpack --config webpack.config.js 34 | ``` 35 | 36 | This will automatically generate the DLL file. For more usage, see [examples](https://github.com/clinyong/dll-link-webpack-plugin/tree/master/examples). 37 | 38 | ### Configuration 39 | 40 | * `htmlMode`: `true` | `false` This is useful when you are using [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin). The DLL file will be included in the output html file. (default is `false`) 41 | * `assetsMode`: `true` | `false` Emit the DLL file as webpack assets file. (default is `false`) 42 | * `appendVersion`: `true` | `false` Append a DLL hash version to your webpack entry filenames. (default is `false`) 43 | 44 | Example for above options: 45 | 46 | ```js 47 | module.exports = { 48 | // ... 49 | plugins: [ 50 | new DllLinkPlugin({ 51 | config: require("webpack.dll.config.js"), 52 | appendVersion: true, 53 | assetsMode: true, 54 | htmlMode: true 55 | }) 56 | ] 57 | }; 58 | ``` 59 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## Install dependency 2 | 3 | Assuming that you are in the project root folder. 4 | 5 | ``` 6 | $ pwd 7 | /Users/Leo/workfile/dll-link-webpack-plugin 8 | ``` 9 | 10 | Run 11 | 12 | ``` 13 | $ yarn 14 | $ cd examples 15 | $ yarn 16 | ``` 17 | 18 | ## Usage 19 | 20 | In the examples folder, run 21 | 22 | ``` 23 | $ npm run xxx 24 | ``` 25 | 26 | `xxx` can be 27 | 28 | * simple 29 | * multiple 30 | * html 31 | -------------------------------------------------------------------------------- /examples/html-webpack-plugin/app.js: -------------------------------------------------------------------------------- 1 | var lib = require("./lib"); 2 | document.body.innerHTML = lib; 3 | -------------------------------------------------------------------------------- /examples/html-webpack-plugin/lib.js: -------------------------------------------------------------------------------- 1 | module.exports = "I am html lib."; 2 | -------------------------------------------------------------------------------- /examples/html-webpack-plugin/webpack.config.js: -------------------------------------------------------------------------------- 1 | var DllLinkPlugin = require("../../"); 2 | var path = require("path"); 3 | var HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | 5 | module.exports = { 6 | entry: { 7 | app: [path.resolve(__dirname, "./app.js")] 8 | }, 9 | output: { 10 | filename: "app.bundle.js", 11 | path: path.resolve(__dirname, "../build") 12 | }, 13 | plugins: [ 14 | new HtmlWebpackPlugin(), 15 | new DllLinkPlugin({ 16 | config: require("./webpack.dll.config"), 17 | htmlMode: true 18 | }) 19 | ] 20 | }; 21 | -------------------------------------------------------------------------------- /examples/html-webpack-plugin/webpack.dll.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var path = require("path"); 3 | 4 | module.exports = { 5 | entry: { 6 | vendor: [path.resolve(__dirname, "lib.js")] 7 | }, 8 | output: { 9 | filename: "vendor.[hash:5].bundle.js", 10 | path: path.resolve(__dirname, "../build"), 11 | library: "vendor_lib" 12 | }, 13 | plugins: [ 14 | new webpack.DllPlugin({ 15 | name: "vendor_lib", 16 | path: path.resolve(__dirname, "../build/vendor-manifest.json") 17 | }) 18 | ] 19 | }; 20 | -------------------------------------------------------------------------------- /examples/multiple-entries/app.js: -------------------------------------------------------------------------------- 1 | var lib = require("./lib1"); 2 | document.getElementById("root").innerHTML = lib; 3 | -------------------------------------------------------------------------------- /examples/multiple-entries/lib1.js: -------------------------------------------------------------------------------- 1 | module.exports = "I am multiple entries lib1."; 2 | -------------------------------------------------------------------------------- /examples/multiple-entries/lib2.js: -------------------------------------------------------------------------------- 1 | module.exports = "I am multiple entries lib2."; 2 | -------------------------------------------------------------------------------- /examples/multiple-entries/webpack.config.js: -------------------------------------------------------------------------------- 1 | var DllLinkPlugin = require("../../"); 2 | var path = require("path"); 3 | 4 | module.exports = { 5 | entry: { 6 | app: [path.resolve(__dirname, "./app.js")] 7 | }, 8 | output: { 9 | filename: "app.bundle.js", 10 | path: path.resolve(__dirname, "../build") 11 | }, 12 | plugins: [ 13 | new DllLinkPlugin({ 14 | config: require("./webpack.dll.config") 15 | }) 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /examples/multiple-entries/webpack.dll.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var path = require("path"); 3 | 4 | module.exports = { 5 | entry: { 6 | vendor: [path.resolve(__dirname, "lib1.js")], 7 | lib2: [path.resolve(__dirname, "lib2.js")] 8 | }, 9 | output: { 10 | filename: "[name].bundle.js", 11 | path: path.resolve(__dirname, "../build"), 12 | library: "vendor_lib" 13 | }, 14 | plugins: [ 15 | new webpack.DllPlugin({ 16 | name: "vendor_lib", 17 | path: path.resolve(__dirname, "../build/[name]-manifest.json") 18 | }) 19 | ] 20 | }; 21 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "simple": 7 | "../node_modules/.bin/webpack --config ./simple/webpack.config.js", 8 | "multiple": 9 | "../node_modules/.bin/webpack --config ./multiple-entries/webpack.config.js", 10 | "html": 11 | "../node_modules/.bin/webpack --config ./html-webpack-plugin/webpack.config.js" 12 | }, 13 | "keywords": [], 14 | "author": "clinyong", 15 | "devDependencies": { 16 | "html-webpack-plugin": "^3.2.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/simple/app.js: -------------------------------------------------------------------------------- 1 | var lib = require("./lib"); 2 | document.getElementById("root").innerHTML = lib; 3 | -------------------------------------------------------------------------------- /examples/simple/lib.js: -------------------------------------------------------------------------------- 1 | module.exports = "I am simple lib."; 2 | -------------------------------------------------------------------------------- /examples/simple/webpack.config.js: -------------------------------------------------------------------------------- 1 | var DllLinkPlugin = require("../../"); 2 | var path = require("path"); 3 | 4 | module.exports = { 5 | entry: { 6 | app: [path.resolve(__dirname, "./app.js")] 7 | }, 8 | output: { 9 | filename: "app.bundle.js", 10 | path: path.resolve(__dirname, "../build") 11 | }, 12 | plugins: [ 13 | new DllLinkPlugin({ 14 | config: require("./webpack.dll.config") 15 | }) 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /examples/simple/webpack.dll.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var path = require("path"); 3 | 4 | module.exports = { 5 | entry: { 6 | vendor: [path.resolve(__dirname, "lib.js")] 7 | }, 8 | output: { 9 | filename: "vendor.bundle.js", 10 | path: path.resolve(__dirname, "../build"), 11 | library: "vendor_lib" 12 | }, 13 | plugins: [ 14 | new webpack.DllPlugin({ 15 | name: "vendor_lib", 16 | path: path.resolve(__dirname, "../build/vendor-manifest.json") 17 | }) 18 | ] 19 | }; 20 | -------------------------------------------------------------------------------- /examples/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | ansi-regex@^2.0.0: 6 | version "2.1.1" 7 | resolved "http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 8 | 9 | big.js@^3.1.3: 10 | version "3.2.0" 11 | resolved "http://registry.npm.taobao.org/big.js/download/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" 12 | 13 | boolbase@~1.0.0: 14 | version "1.0.0" 15 | resolved "http://registry.npm.taobao.org/boolbase/download/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" 16 | 17 | camel-case@3.0.x: 18 | version "3.0.0" 19 | resolved "http://registry.npm.taobao.org/camel-case/download/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" 20 | dependencies: 21 | no-case "^2.2.0" 22 | upper-case "^1.1.1" 23 | 24 | clean-css@4.1.x: 25 | version "4.1.11" 26 | resolved "http://registry.npm.taobao.org/clean-css/download/clean-css-4.1.11.tgz#2ecdf145aba38f54740f26cefd0ff3e03e125d6a" 27 | dependencies: 28 | source-map "0.5.x" 29 | 30 | commander@2.15.x, commander@~2.15.0: 31 | version "2.15.1" 32 | resolved "http://registry.npm.taobao.org/commander/download/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" 33 | 34 | core-util-is@~1.0.0: 35 | version "1.0.2" 36 | resolved "http://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 37 | 38 | css-select@^1.1.0: 39 | version "1.2.0" 40 | resolved "http://registry.npm.taobao.org/css-select/download/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" 41 | dependencies: 42 | boolbase "~1.0.0" 43 | css-what "2.1" 44 | domutils "1.5.1" 45 | nth-check "~1.0.1" 46 | 47 | css-what@2.1: 48 | version "2.1.0" 49 | resolved "http://registry.npm.taobao.org/css-what/download/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" 50 | 51 | define-properties@^1.1.2: 52 | version "1.1.2" 53 | resolved "http://registry.npm.taobao.org/define-properties/download/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" 54 | dependencies: 55 | foreach "^2.0.5" 56 | object-keys "^1.0.8" 57 | 58 | dom-converter@~0.1: 59 | version "0.1.4" 60 | resolved "http://registry.npm.taobao.org/dom-converter/download/dom-converter-0.1.4.tgz#a45ef5727b890c9bffe6d7c876e7b19cb0e17f3b" 61 | dependencies: 62 | utila "~0.3" 63 | 64 | dom-serializer@0: 65 | version "0.1.0" 66 | resolved "http://registry.npm.taobao.org/dom-serializer/download/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" 67 | dependencies: 68 | domelementtype "~1.1.1" 69 | entities "~1.1.1" 70 | 71 | domelementtype@1: 72 | version "1.3.0" 73 | resolved "http://registry.npm.taobao.org/domelementtype/download/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" 74 | 75 | domelementtype@~1.1.1: 76 | version "1.1.3" 77 | resolved "http://registry.npm.taobao.org/domelementtype/download/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" 78 | 79 | domhandler@2.1: 80 | version "2.1.0" 81 | resolved "http://registry.npm.taobao.org/domhandler/download/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594" 82 | dependencies: 83 | domelementtype "1" 84 | 85 | domutils@1.1: 86 | version "1.1.6" 87 | resolved "http://registry.npm.taobao.org/domutils/download/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" 88 | dependencies: 89 | domelementtype "1" 90 | 91 | domutils@1.5.1: 92 | version "1.5.1" 93 | resolved "http://registry.npm.taobao.org/domutils/download/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" 94 | dependencies: 95 | dom-serializer "0" 96 | domelementtype "1" 97 | 98 | emojis-list@^2.0.0: 99 | version "2.1.0" 100 | resolved "http://registry.npm.taobao.org/emojis-list/download/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" 101 | 102 | entities@~1.1.1: 103 | version "1.1.1" 104 | resolved "http://registry.npm.taobao.org/entities/download/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" 105 | 106 | es-abstract@^1.5.1: 107 | version "1.11.0" 108 | resolved "http://registry.npm.taobao.org/es-abstract/download/es-abstract-1.11.0.tgz#cce87d518f0496893b1a30cd8461835535480681" 109 | dependencies: 110 | es-to-primitive "^1.1.1" 111 | function-bind "^1.1.1" 112 | has "^1.0.1" 113 | is-callable "^1.1.3" 114 | is-regex "^1.0.4" 115 | 116 | es-to-primitive@^1.1.1: 117 | version "1.1.1" 118 | resolved "http://registry.npm.taobao.org/es-to-primitive/download/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" 119 | dependencies: 120 | is-callable "^1.1.1" 121 | is-date-object "^1.0.1" 122 | is-symbol "^1.0.1" 123 | 124 | foreach@^2.0.5: 125 | version "2.0.5" 126 | resolved "http://registry.npm.taobao.org/foreach/download/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" 127 | 128 | function-bind@^1.0.2, function-bind@^1.1.1: 129 | version "1.1.1" 130 | resolved "http://registry.npm.taobao.org/function-bind/download/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 131 | 132 | has@^1.0.1: 133 | version "1.0.1" 134 | resolved "http://registry.npm.taobao.org/has/download/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" 135 | dependencies: 136 | function-bind "^1.0.2" 137 | 138 | he@1.1.x: 139 | version "1.1.1" 140 | resolved "http://registry.npm.taobao.org/he/download/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" 141 | 142 | html-minifier@^3.2.3: 143 | version "3.5.15" 144 | resolved "http://registry.npm.taobao.org/html-minifier/download/html-minifier-3.5.15.tgz#f869848d4543cbfd84f26d5514a2a87cbf9a05e0" 145 | dependencies: 146 | camel-case "3.0.x" 147 | clean-css "4.1.x" 148 | commander "2.15.x" 149 | he "1.1.x" 150 | param-case "2.1.x" 151 | relateurl "0.2.x" 152 | uglify-js "3.3.x" 153 | 154 | html-webpack-plugin@^3.2.0: 155 | version "3.2.0" 156 | resolved "http://registry.npm.taobao.org/html-webpack-plugin/download/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b" 157 | dependencies: 158 | html-minifier "^3.2.3" 159 | loader-utils "^0.2.16" 160 | lodash "^4.17.3" 161 | pretty-error "^2.0.2" 162 | tapable "^1.0.0" 163 | toposort "^1.0.0" 164 | util.promisify "1.0.0" 165 | 166 | htmlparser2@~3.3.0: 167 | version "3.3.0" 168 | resolved "http://registry.npm.taobao.org/htmlparser2/download/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" 169 | dependencies: 170 | domelementtype "1" 171 | domhandler "2.1" 172 | domutils "1.1" 173 | readable-stream "1.0" 174 | 175 | inherits@~2.0.1: 176 | version "2.0.3" 177 | resolved "http://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 178 | 179 | is-callable@^1.1.1, is-callable@^1.1.3: 180 | version "1.1.3" 181 | resolved "http://registry.npm.taobao.org/is-callable/download/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" 182 | 183 | is-date-object@^1.0.1: 184 | version "1.0.1" 185 | resolved "http://registry.npm.taobao.org/is-date-object/download/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" 186 | 187 | is-regex@^1.0.4: 188 | version "1.0.4" 189 | resolved "http://registry.npm.taobao.org/is-regex/download/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" 190 | dependencies: 191 | has "^1.0.1" 192 | 193 | is-symbol@^1.0.1: 194 | version "1.0.1" 195 | resolved "http://registry.npm.taobao.org/is-symbol/download/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" 196 | 197 | isarray@0.0.1: 198 | version "0.0.1" 199 | resolved "http://registry.npm.taobao.org/isarray/download/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 200 | 201 | json5@^0.5.0: 202 | version "0.5.1" 203 | resolved "http://registry.npm.taobao.org/json5/download/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" 204 | 205 | loader-utils@^0.2.16: 206 | version "0.2.17" 207 | resolved "http://registry.npm.taobao.org/loader-utils/download/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" 208 | dependencies: 209 | big.js "^3.1.3" 210 | emojis-list "^2.0.0" 211 | json5 "^0.5.0" 212 | object-assign "^4.0.1" 213 | 214 | lodash@^4.17.3: 215 | version "4.17.10" 216 | resolved "http://registry.npm.taobao.org/lodash/download/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" 217 | 218 | lower-case@^1.1.1: 219 | version "1.1.4" 220 | resolved "http://registry.npm.taobao.org/lower-case/download/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" 221 | 222 | no-case@^2.2.0: 223 | version "2.3.2" 224 | resolved "http://registry.npm.taobao.org/no-case/download/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" 225 | dependencies: 226 | lower-case "^1.1.1" 227 | 228 | nth-check@~1.0.1: 229 | version "1.0.1" 230 | resolved "http://registry.npm.taobao.org/nth-check/download/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" 231 | dependencies: 232 | boolbase "~1.0.0" 233 | 234 | object-assign@^4.0.1: 235 | version "4.1.1" 236 | resolved "http://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 237 | 238 | object-keys@^1.0.8: 239 | version "1.0.11" 240 | resolved "http://registry.npm.taobao.org/object-keys/download/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" 241 | 242 | object.getownpropertydescriptors@^2.0.3: 243 | version "2.0.3" 244 | resolved "http://registry.npm.taobao.org/object.getownpropertydescriptors/download/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" 245 | dependencies: 246 | define-properties "^1.1.2" 247 | es-abstract "^1.5.1" 248 | 249 | param-case@2.1.x: 250 | version "2.1.1" 251 | resolved "http://registry.npm.taobao.org/param-case/download/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" 252 | dependencies: 253 | no-case "^2.2.0" 254 | 255 | pretty-error@^2.0.2: 256 | version "2.1.1" 257 | resolved "http://registry.npm.taobao.org/pretty-error/download/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" 258 | dependencies: 259 | renderkid "^2.0.1" 260 | utila "~0.4" 261 | 262 | readable-stream@1.0: 263 | version "1.0.34" 264 | resolved "http://registry.npm.taobao.org/readable-stream/download/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" 265 | dependencies: 266 | core-util-is "~1.0.0" 267 | inherits "~2.0.1" 268 | isarray "0.0.1" 269 | string_decoder "~0.10.x" 270 | 271 | relateurl@0.2.x: 272 | version "0.2.7" 273 | resolved "http://registry.npm.taobao.org/relateurl/download/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" 274 | 275 | renderkid@^2.0.1: 276 | version "2.0.1" 277 | resolved "http://registry.npm.taobao.org/renderkid/download/renderkid-2.0.1.tgz#898cabfc8bede4b7b91135a3ffd323e58c0db319" 278 | dependencies: 279 | css-select "^1.1.0" 280 | dom-converter "~0.1" 281 | htmlparser2 "~3.3.0" 282 | strip-ansi "^3.0.0" 283 | utila "~0.3" 284 | 285 | source-map@0.5.x: 286 | version "0.5.7" 287 | resolved "http://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 288 | 289 | source-map@~0.6.1: 290 | version "0.6.1" 291 | resolved "http://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 292 | 293 | string_decoder@~0.10.x: 294 | version "0.10.31" 295 | resolved "http://registry.npm.taobao.org/string_decoder/download/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 296 | 297 | strip-ansi@^3.0.0: 298 | version "3.0.1" 299 | resolved "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 300 | dependencies: 301 | ansi-regex "^2.0.0" 302 | 303 | tapable@^1.0.0: 304 | version "1.0.0" 305 | resolved "http://registry.npm.taobao.org/tapable/download/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2" 306 | 307 | toposort@^1.0.0: 308 | version "1.0.7" 309 | resolved "http://registry.npm.taobao.org/toposort/download/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" 310 | 311 | uglify-js@3.3.x: 312 | version "3.3.23" 313 | resolved "http://registry.npm.taobao.org/uglify-js/download/uglify-js-3.3.23.tgz#48ea43e638364d18be292a6fdc2b5b7c35f239ab" 314 | dependencies: 315 | commander "~2.15.0" 316 | source-map "~0.6.1" 317 | 318 | upper-case@^1.1.1: 319 | version "1.1.3" 320 | resolved "http://registry.npm.taobao.org/upper-case/download/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" 321 | 322 | util.promisify@1.0.0: 323 | version "1.0.0" 324 | resolved "http://registry.npm.taobao.org/util.promisify/download/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" 325 | dependencies: 326 | define-properties "^1.1.2" 327 | object.getownpropertydescriptors "^2.0.3" 328 | 329 | utila@~0.3: 330 | version "0.3.3" 331 | resolved "http://registry.npm.taobao.org/utila/download/utila-0.3.3.tgz#d7e8e7d7e309107092b05f8d9688824d633a4226" 332 | 333 | utila@~0.4: 334 | version "0.4.0" 335 | resolved "http://registry.npm.taobao.org/utila/download/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" 336 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { DllLinkWebpackPlugin } from "./dist/index.d"; 2 | export = DllLinkWebpackPlugin; 3 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ["ts", "tsx", "js"], 3 | transform: { 4 | "^.+\\.(ts|tsx)$": "ts-jest" 5 | }, 6 | testMatch: ["/tests/**/*.test.+(ts)"], 7 | testEnvironment: "node" 8 | }; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dll-link-webpack-plugin", 3 | "version": "3.2.1", 4 | "description": "a webpack plugin aims to replace DllReferencePlugin", 5 | "main": "dist/index.js", 6 | "typings": "index.d.ts", 7 | "author": "clinyong ", 8 | "license": "MIT", 9 | "scripts": { 10 | "prepare": "tsc", 11 | "test": "jest" 12 | }, 13 | "keywords": [ 14 | "webpack", 15 | "webpack plugin", 16 | "webpack dll", 17 | "DllPlugin", 18 | "DllReferencePlugin" 19 | ], 20 | "devDependencies": { 21 | "@types/fs-extra": "^5.0.2", 22 | "@types/jest": "^22.2.3", 23 | "@types/node": "^7.0.12", 24 | "@types/webpack": "^2.2.14", 25 | "husky": "^1.0.0-rc.2", 26 | "jest": "^22.4.3", 27 | "prettier": "^1.12.1", 28 | "pretty-quick": "^1.4.1", 29 | "ts-jest": "^22.4.6", 30 | "tslint": "^5.1.0", 31 | "typescript": "^2.4.2", 32 | "webpack": "^4.6.0", 33 | "webpack-cli": "^2.1.2" 34 | }, 35 | "dependencies": { 36 | "@fmtk/package-list": "^2.1.0", 37 | "chalk": "^1.1.3", 38 | "fs-extra": "^5.0.0", 39 | "invariant": "^2.2.2", 40 | "lodash": "^4.17.4", 41 | "md5": "^2.2.1", 42 | "strip-bom": "^3.0.0" 43 | }, 44 | "repository": { 45 | "type": "git", 46 | "url": "https://github.com/clinyong/dll-link-webpack-plugin" 47 | }, 48 | "files": ["index.d.ts", "dist/"], 49 | "husky": { 50 | "hooks": { 51 | "pre-commit": "pretty-quick --staged" 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/BundleController.ts: -------------------------------------------------------------------------------- 1 | import * as webpack from "webpack"; 2 | import * as fs from "fs-extra"; 3 | import * as path from "path"; 4 | 5 | const FS_ACCURACY = 10000; 6 | 7 | interface OutputFiles { 8 | jsNames: string[]; 9 | jsonNames: string[]; 10 | } 11 | 12 | interface OutputPathItem { 13 | src: string; 14 | dist: string; 15 | } 16 | 17 | interface OutputPath { 18 | js: OutputPathItem; 19 | json: string; 20 | } 21 | 22 | export interface CacheConfig { 23 | cacheJSPath: string; 24 | cacheJSONPath: string; 25 | cacheJSNames: string[]; 26 | } 27 | 28 | export interface BundleOptions { 29 | webpackConfig: webpack.Configuration; 30 | cacheConfig: CacheConfig; 31 | manifestNames?: string[]; 32 | } 33 | 34 | export class BundleController { 35 | private webpackConfig: webpack.Configuration; 36 | private outputFiles: OutputFiles; 37 | private outputPath: OutputPath; 38 | private manifestNames: string[]; 39 | private referencePlugins: webpack.DllReferencePlugin[]; 40 | private pluginStartTime: number; 41 | 42 | constructor(options: BundleOptions) { 43 | const { webpackConfig, manifestNames, cacheConfig } = options; 44 | this.manifestNames = options.manifestNames || []; 45 | 46 | const { output, entry, plugins } = webpackConfig; 47 | let index = -1; 48 | for (let i = 0; i < plugins.length; i++) { 49 | if (plugins[i] instanceof webpack.DllPlugin) { 50 | index = i; 51 | break; 52 | } 53 | } 54 | 55 | if (index === -1) { 56 | throw new Error("Your webpack dll config miss DllPlugin."); 57 | } 58 | 59 | const dllPlugin: any = plugins[index]; 60 | const dllOptions: webpack.DllPlugin.Options = dllPlugin.options; 61 | const dllJsonFullPath = dllOptions.path; 62 | const jsonNameTPL = path.basename(dllJsonFullPath); 63 | dllPlugin.options.path = path.join( 64 | cacheConfig.cacheJSONPath, 65 | jsonNameTPL 66 | ); 67 | webpackConfig.plugins[index] = dllPlugin; 68 | 69 | let outputJsonNames = []; 70 | Object.keys(entry).forEach(entryName => { 71 | outputJsonNames.push(jsonNameTPL.replace("[name]", entryName)); 72 | }); 73 | 74 | this.outputFiles = { 75 | jsNames: [], 76 | jsonNames: outputJsonNames 77 | }; 78 | this.updateOutputJSNames(cacheConfig.cacheJSNames); 79 | this.outputPath = { 80 | js: { dist: output.path, src: cacheConfig.cacheJSPath }, 81 | json: cacheConfig.cacheJSONPath 82 | }; 83 | 84 | this.initDllReferencePlugins(manifestNames, dllOptions); 85 | 86 | webpackConfig.output.path = this.outputPath.js.src; 87 | this.webpackConfig = webpackConfig; 88 | 89 | this.pluginStartTime = Date.now(); 90 | } 91 | 92 | private initDllReferencePlugins( 93 | manifestNames: string[], 94 | dllOptions: webpack.DllPlugin.Options 95 | ) { 96 | let referenceNames = manifestNames || this.outputFiles.jsonNames; 97 | let referenceConf: webpack.DllReferencePlugin.Options[] = referenceNames.map( 98 | name => 99 | ({ 100 | manifest: path.join(this.outputPath.json, name) 101 | } as any) 102 | ); 103 | if (dllOptions.context) { 104 | referenceConf = referenceConf.map(conf => ({ 105 | ...conf, 106 | context: dllOptions.context 107 | })); 108 | } 109 | this.referencePlugins = referenceConf.map( 110 | conf => new webpack.DllReferencePlugin(conf) 111 | ); 112 | } 113 | 114 | private modifyGenerateFileModifyTime() { 115 | let names = [ 116 | ...this.outputFiles.jsNames.map(name => 117 | path.join(this.outputPath.js.src, name) 118 | ), 119 | ...this.outputFiles.jsonNames.map(name => 120 | path.join(this.outputPath.json, name) 121 | ) 122 | ]; 123 | const time = parseInt( 124 | Math.floor((this.pluginStartTime - FS_ACCURACY) / 1000).toFixed() 125 | ); 126 | names.forEach(name => { 127 | fs.utimesSync(name, time, time); 128 | }); 129 | } 130 | 131 | private updateOutputJSNames(outputNames) { 132 | const list = this.manifestNames 133 | .map(name => 134 | outputNames.find(cacheName => cacheName.indexOf(name) !== -1) 135 | ) 136 | .filter(name => !!name); 137 | 138 | this.outputFiles.jsNames = list.length > 0 ? list : outputNames; 139 | } 140 | 141 | public applyDllReferencePlugins(compiler) { 142 | this.referencePlugins.forEach(plugin => { 143 | plugin.apply.call(plugin, compiler); 144 | }); 145 | } 146 | 147 | public copyAllFiles() { 148 | const { dist, src } = this.outputPath.js; 149 | this.outputFiles.jsNames.forEach(name => { 150 | fs.copySync(path.join(src, name), path.join(dist, name), { 151 | preserveTimestamps: true 152 | }); 153 | }); 154 | } 155 | 156 | public webpackBuild() { 157 | return new Promise((resolve, reject) => { 158 | webpack(this.webpackConfig, (err, stats) => { 159 | if (err) { 160 | reject(err); 161 | } else if (stats.hasErrors()) { 162 | reject(new Error(stats.toJson().errors.join("\n"))); 163 | } else { 164 | const assets = stats 165 | .toJson() 166 | .assets.map(asset => asset.name); 167 | this.modifyGenerateFileModifyTime(); 168 | this.updateOutputJSNames(assets); 169 | resolve(assets); 170 | } 171 | }); 172 | }); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/CacheController.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs-extra"; 2 | import * as webpack from "webpack"; 3 | import * as md5 from "md5"; 4 | import * as chalk from "chalk"; 5 | import { 6 | getDependencyFromYarn, 7 | PackageDependency, 8 | getPKGVersion 9 | } from "./utils/packageDependency"; 10 | 11 | function isVersionEqual( 12 | versionA: PackageDependency, 13 | versionB: PackageDependency 14 | ) { 15 | if (!versionA && !versionB) { 16 | return true; 17 | } else if (versionA && versionB) { 18 | return Object.keys(versionA).every( 19 | k => 20 | versionB[k] && 21 | versionA[k].version === versionB[k].version && 22 | isVersionEqual( 23 | versionA[k].dependencies, 24 | versionB[k].dependencies 25 | ) 26 | ); 27 | } else { 28 | return false; 29 | } 30 | } 31 | 32 | // just check entry version, not include entry dependency. 33 | function shadowCheckEntryVersion(entryVersion: PackageDependency) { 34 | return Object.keys(entryVersion).every( 35 | k => entryVersion[k].version === getPKGVersion(k) 36 | ); 37 | } 38 | 39 | export type DllEntry = string | string[] | webpack.Entry; 40 | 41 | export interface DllConfigFile { 42 | outputJSNames: string[]; 43 | entryVersion: PackageDependency; 44 | } 45 | 46 | export interface ManifestCache { 47 | configFiles: { [index: string]: DllConfigFile }; 48 | } 49 | 50 | export interface CacheOptions { 51 | configIndex: string; 52 | entry: DllEntry; 53 | manifestFile: string; 54 | } 55 | 56 | export class CacheController { 57 | private manifestCache: ManifestCache; 58 | private currentConfigContent: DllConfigFile; 59 | private configIndex: string; 60 | private shouldUpdate: boolean; 61 | private manifestFile: string; 62 | 63 | constructor(options: CacheOptions) { 64 | const { configIndex, manifestFile } = options; 65 | 66 | this.configIndex = configIndex; 67 | 68 | this.manifestFile = manifestFile; 69 | 70 | this.readCacheFile(); 71 | this.checkCache(options.entry); 72 | } 73 | 74 | private readCacheFile() { 75 | try { 76 | const content = fs.readFileSync(this.manifestFile); 77 | this.manifestCache = JSON.parse(content.toString()); 78 | } catch (e) { 79 | this.manifestCache = { 80 | configFiles: {} 81 | }; 82 | } 83 | 84 | this.currentConfigContent = this.manifestCache.configFiles[ 85 | this.configIndex 86 | ] || { outputJSNames: [], entryVersion: null }; 87 | } 88 | 89 | private checkCache(entry: DllEntry) { 90 | const entryVersion = getDependencyFromYarn(entry); 91 | 92 | const isYarnVersionRight = 93 | entryVersion && shadowCheckEntryVersion(entryVersion); 94 | 95 | if (entryVersion && !isYarnVersionRight) { 96 | console.log( 97 | chalk.yellow( 98 | "[dll-link-plugin]: Version in yarn is different from node_modules. Please reinstall package." 99 | ) 100 | ); 101 | } 102 | 103 | if (isYarnVersionRight) { 104 | this.shouldUpdate = 105 | this.currentConfigContent.outputJSNames.length === 0 || 106 | !isVersionEqual( 107 | this.currentConfigContent.entryVersion, 108 | entryVersion 109 | ); 110 | this.currentConfigContent.entryVersion = entryVersion; 111 | } else { 112 | this.shouldUpdate = true; 113 | } 114 | } 115 | 116 | public writeCache() { 117 | fs.writeFileSync(this.manifestFile, JSON.stringify(this.manifestCache)); 118 | } 119 | 120 | public updateJSNamesCache(val: string[]) { 121 | this.manifestCache.configFiles[ 122 | this.configIndex 123 | ] = this.currentConfigContent = Object.assign( 124 | {}, 125 | this.currentConfigContent, 126 | { outputJSNames: val } 127 | ); 128 | } 129 | 130 | public getCacheJSNames() { 131 | return this.currentConfigContent.outputJSNames; 132 | } 133 | 134 | public shouldUpdateCache() { 135 | return this.shouldUpdate; 136 | } 137 | 138 | public getCacheVersion() { 139 | const jsNames = this.currentConfigContent.outputJSNames.join(";"); 140 | return md5(jsNames).slice(0, 6); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as _ from "lodash"; 3 | import * as md5 from "md5"; 4 | import * as fs from "fs-extra"; 5 | import * as webpack from "webpack"; 6 | import * as chalk from "chalk"; 7 | import { CacheController } from "./CacheController"; 8 | import { BundleController } from "./BundleController"; 9 | 10 | const cacheDir = path.resolve(".dll-link-plugin"); 11 | const MANIFEST_FILE = "manifest.json"; 12 | const pluginName = "DllLinkWebpackPlugin"; 13 | 14 | export interface Output { 15 | jsNames: string[]; 16 | jsPath: string; 17 | jsonNames: string[]; 18 | jsonPath: string; 19 | } 20 | 21 | export interface DllLinkWebpackPluginOptions { 22 | config: webpack.Configuration; 23 | HtmlWebpackPlugin?: any; 24 | manifestNames?: string[]; 25 | assetsMode?: boolean; 26 | htmlMode?: boolean; 27 | appendVersion?: boolean; 28 | } 29 | 30 | function md5Slice(msg) { 31 | return md5(msg).slice(0, 10); 32 | } 33 | 34 | function changeName(name: string, version: string) { 35 | const tmp = name.split("."); 36 | const ext = tmp.splice(-1); 37 | if (ext[0] === "js") { 38 | return `${tmp.join(".")}.${version}.js`; 39 | } else { 40 | return name; 41 | } 42 | } 43 | 44 | /** 45 | * Takes a string in train case and transforms it to camel case 46 | * 47 | * Example: 'hello-my-world' to 'helloMyWorld' 48 | * 49 | * @param {string} word 50 | */ 51 | function trainCaseToCamelCase(word: string) { 52 | return word.replace(/-([\w])/g, function(match, p1) { 53 | return p1.toUpperCase(); 54 | }); 55 | } 56 | 57 | // str1 may be a http url, so we can not use path.join here. 58 | function joinString(str1: string, str2: string) { 59 | if (str1 === "") { 60 | return str2; 61 | } 62 | 63 | let newStr1 = str1.endsWith("/") ? str1.slice(0, str1.length - 1) : str1; 64 | let newStr2 = str2.startsWith("/") ? str2.slice(1) : str2; 65 | 66 | return `${newStr1}/${newStr2}`; 67 | } 68 | 69 | export class DllLinkWebpackPlugin { 70 | cacheController: CacheController; 71 | bundleController: BundleController; 72 | hasCompile: boolean; 73 | cacheJSPath: string; 74 | cacheJSONPath: string; 75 | options: DllLinkWebpackPluginOptions; 76 | 77 | constructor(options: DllLinkWebpackPluginOptions) { 78 | this.check = this.check.bind(this); 79 | this.addAssets = this.addAssets.bind(this); 80 | this.hookIntoHTML = this.hookIntoHTML.bind(this); 81 | this.updateNames = this.updateNames.bind(this); 82 | 83 | this.options = options; 84 | 85 | const { config, manifestNames } = this.options; 86 | if (manifestNames && !_.isArray(manifestNames)) { 87 | throw new Error("manifest names must be an array."); 88 | } 89 | 90 | const { entry } = config; 91 | 92 | const configIndex = md5Slice(JSON.stringify(config)); 93 | this.cacheJSPath = path.join(cacheDir, configIndex, "js"); 94 | this.cacheJSONPath = path.join(cacheDir, configIndex, "json"); 95 | 96 | this.cacheController = new CacheController({ 97 | configIndex, 98 | entry, 99 | manifestFile: path.join(cacheDir, MANIFEST_FILE) 100 | }); 101 | this.bundleController = new BundleController({ 102 | webpackConfig: config, 103 | cacheConfig: { 104 | cacheJSNames: this.cacheController.getCacheJSNames(), 105 | cacheJSPath: this.cacheJSPath, 106 | cacheJSONPath: this.cacheJSONPath 107 | }, 108 | manifestNames 109 | }); 110 | this.hasCompile = false; 111 | } 112 | 113 | hookIntoHTML(compilation) { 114 | const hookFunction = (htmlPluginData, cb) => { 115 | const { publicPath = "" } = this.options.config.output; 116 | const jsFiles = [], 117 | cssFiles = []; 118 | 119 | this.cacheController.getCacheJSNames().forEach(item => { 120 | const ext = item.split(".").reverse()[0]; 121 | if (ext === "js") { 122 | jsFiles.push(joinString(publicPath, item)); 123 | } else if (ext === "css") { 124 | cssFiles.push(joinString(publicPath, item)); 125 | } 126 | }); 127 | 128 | const assets = htmlPluginData.assets; 129 | assets.js = jsFiles.concat(assets.js); 130 | assets.css = cssFiles.concat(assets.css); 131 | 132 | cb(null, htmlPluginData); 133 | }; 134 | if (compilation.hooks) { 135 | if (compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration) { 136 | compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration.tapAsync( 137 | pluginName, 138 | hookFunction 139 | ); 140 | } else if (this.options.HtmlWebpackPlugin) { 141 | // HtmlWebPackPlugin 4.x 142 | const hooks = this.options.HtmlWebpackPlugin.getHooks( 143 | compilation 144 | ); 145 | hooks.beforeAssetTagGeneration.tapAsync( 146 | pluginName, 147 | hookFunction 148 | ); 149 | } 150 | } else { 151 | compilation.plugin( 152 | "html-webpack-plugin-before-html-generation", 153 | hookFunction 154 | ); 155 | } 156 | } 157 | 158 | addAssets(compilation, cb) { 159 | this.cacheController.getCacheJSNames().map(name => { 160 | const source = fs 161 | .readFileSync(`${this.cacheJSPath}/${name}`) 162 | .toString(); 163 | compilation.assets[name] = { 164 | source: () => source, 165 | size: () => source.length 166 | }; 167 | }); 168 | 169 | return cb(); 170 | } 171 | 172 | async check(compilation, cb) { 173 | if (!this.hasCompile) { 174 | this.hasCompile = true; 175 | if (this.cacheController.shouldUpdateCache()) { 176 | console.log(); 177 | console.log(chalk.cyan("[dll-link-plugin]: Rebuilding dll.")); 178 | console.log(); 179 | 180 | let assets = []; 181 | try { 182 | assets = await this.bundleController.webpackBuild(); 183 | } catch (err) { 184 | return cb(err); 185 | } 186 | this.cacheController.updateJSNamesCache(assets); 187 | } 188 | 189 | const { htmlMode, assetsMode } = this.options; 190 | if (!htmlMode && !assetsMode) { 191 | this.bundleController.copyAllFiles(); 192 | } 193 | 194 | this.cacheController.writeCache(); 195 | } 196 | return cb(); 197 | } 198 | 199 | updateNames(compilation, cb) { 200 | const ver = this.cacheController.getCacheVersion(); 201 | 202 | let entryChunks = {}; 203 | 204 | // change related chunks name 205 | const chunks = compilation.chunks as any[]; 206 | for (let i = 0; i < chunks.length; i++) { 207 | const chunk = chunks[i]; 208 | if ( 209 | (typeof chunk.isInitial === "function" && chunk.isInitial()) || 210 | chunk.isInitial === true 211 | ) { 212 | chunk.files = chunk.files.map(file => { 213 | entryChunks[file] = true; 214 | return changeName(file, ver); 215 | }); 216 | } 217 | } 218 | 219 | // change assets name 220 | const newAssets = {}; 221 | Object.keys(compilation.assets).forEach(k => { 222 | let newKey = k; 223 | if (entryChunks[k]) { 224 | newKey = changeName(k, ver); 225 | } 226 | newAssets[newKey] = compilation.assets[k]; 227 | }); 228 | compilation.assets = newAssets; 229 | 230 | return cb(); 231 | } 232 | 233 | attachCompiler(compiler, eventName: string, isAsync: boolean, func) { 234 | if ("hooks" in compiler) { 235 | // webpack 4 236 | eventName = trainCaseToCamelCase(eventName); 237 | if (compiler.hooks[eventName]) { 238 | compiler.hooks[eventName][isAsync ? "tapAsync" : "tap"]( 239 | pluginName, 240 | func 241 | ); 242 | } 243 | } else { 244 | // webpack 2/3 245 | compiler.plugin(eventName, func); 246 | } 247 | } 248 | 249 | apply(compiler) { 250 | const { htmlMode, assetsMode, appendVersion } = this.options; 251 | this.attachCompiler(compiler, "before-compile", true, this.check); 252 | if (htmlMode) { 253 | // Hook into the html-webpack-plugin processing 254 | this.attachCompiler( 255 | compiler, 256 | "compilation", 257 | false, 258 | this.hookIntoHTML 259 | ); 260 | } 261 | 262 | if (appendVersion) { 263 | this.attachCompiler(compiler, "emit", true, this.updateNames); 264 | } 265 | 266 | if (htmlMode || assetsMode) { 267 | this.attachCompiler(compiler, "emit", true, this.addAssets); 268 | } 269 | 270 | this.bundleController.applyDllReferencePlugins(compiler); 271 | } 272 | } 273 | 274 | module.exports = DllLinkWebpackPlugin; 275 | -------------------------------------------------------------------------------- /src/utils/packageDependency.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { getPackageList } from "@fmtk/package-list"; 3 | 4 | const NODE_MODULES_PATH = path.resolve("./node_modules"); 5 | 6 | export interface YarnDependency { 7 | version: string; 8 | dependencies?: PackageDependency; 9 | } 10 | 11 | export interface PackageDependency { 12 | [index: string]: YarnDependency; 13 | } 14 | 15 | function convertEntryToList(entry: any): string[] { 16 | if (typeof entry === "string") { 17 | return [entry]; 18 | } else if (Array.isArray(entry)) { 19 | return entry; 20 | } else if (typeof entry === "object") { 21 | let list = []; 22 | Object.keys(entry).forEach(k => { 23 | list = list.concat(entry[k]); 24 | }); 25 | return list; 26 | } else { 27 | throw `Incorrect entry type.`; 28 | } 29 | } 30 | 31 | export function getDependencyFromYarn(entry: any): PackageDependency | null { 32 | let entryList = convertEntryToList(entry); 33 | const packages = getPackageList(); 34 | if (!packages) { 35 | return null; 36 | } 37 | 38 | const root = packages[".@."]; 39 | if (!root || !root.dependencies) { 40 | return; 41 | } 42 | 43 | entryList = entryList 44 | .map(item => { 45 | const version = root.dependencies[item]; 46 | return version ? `${item}@${version}` : ""; 47 | }) 48 | .filter(item => !!item); 49 | 50 | function findDependency( 51 | entryList: string[], 52 | history?: string[] 53 | ): PackageDependency { 54 | let m: PackageDependency = {}; 55 | entryList.map(k => { 56 | if (history && history.indexOf(k) >= 0) { 57 | // skip circular dependency 58 | return; 59 | } 60 | const info = packages[k]; 61 | let item: YarnDependency = { 62 | version: info.version 63 | }; 64 | if (info.dependencies) { 65 | item.dependencies = findDependency( 66 | Object.keys(info.dependencies).map( 67 | k => `${k}@${info.dependencies[k]}` 68 | ), 69 | history ? [...history, k] : [k] 70 | ); 71 | } 72 | 73 | m[k] = item; 74 | }); 75 | return m; 76 | } 77 | 78 | return findDependency(entryList); 79 | } 80 | 81 | export function getPKGVersion(yarnEntryName: string) { 82 | const atIndex = yarnEntryName.lastIndexOf("@"); 83 | if (atIndex > 0) { 84 | yarnEntryName = yarnEntryName.substring(0, atIndex); 85 | } 86 | const pkgPath = path.join(NODE_MODULES_PATH, yarnEntryName, "package.json"); 87 | const pkg = require(pkgPath); 88 | 89 | return pkg.version; 90 | } 91 | -------------------------------------------------------------------------------- /tests/BundleController.test.ts: -------------------------------------------------------------------------------- 1 | import { BundleController } from "../src/BundleController"; 2 | import * as fse from "fs-extra"; 3 | import * as path from "path"; 4 | import * as webpack from "webpack"; 5 | 6 | const join = path.join; 7 | 8 | const libraryName = "vendor_lib"; 9 | 10 | const outputFileName = "dll.bundle.js"; 11 | const manifestJSONName = "vendor-manifest.json"; 12 | 13 | const entryFilePath = path.resolve(__dirname, `dll.js`); 14 | const bundleOutputPath = join(__dirname, "bundle-output"); 15 | const cacheJSPath = join(bundleOutputPath, "cache/js"); 16 | const cacheJSONPath = join(bundleOutputPath, "cache/json"); 17 | 18 | const webpackConfig: webpack.Configuration = { 19 | entry: [entryFilePath], 20 | output: { 21 | filename: outputFileName, 22 | path: bundleOutputPath, 23 | library: libraryName 24 | }, 25 | plugins: [ 26 | new webpack.DllPlugin({ 27 | name: libraryName, 28 | path: path.join(bundleOutputPath, manifestJSONName) 29 | }) 30 | ] 31 | }; 32 | 33 | const bundle = new BundleController({ 34 | webpackConfig, 35 | cacheConfig: { 36 | cacheJSNames: [outputFileName], 37 | cacheJSPath, 38 | cacheJSONPath 39 | } 40 | }); 41 | 42 | beforeAll(() => 43 | fse.writeFile(entryFilePath, "console.log('lib.js');", { 44 | encoding: "utf8" 45 | })); 46 | 47 | afterAll(async () => { 48 | await fse.remove(entryFilePath); 49 | await fse.remove(bundleOutputPath); 50 | }); 51 | 52 | describe("test build", () => { 53 | const cacheFileExits = async () => 54 | (await fse.exists(join(cacheJSPath, outputFileName))) && 55 | (await fse.exists(join(cacheJSONPath, manifestJSONName))); 56 | const outputFileExits = async () => 57 | fse.exists(join(bundleOutputPath, outputFileName)); 58 | 59 | test("empty files", async () => { 60 | expect(await cacheFileExits()).toBeFalsy(); 61 | expect(await outputFileExits()).toBeFalsy(); 62 | }); 63 | 64 | test("build without error", () => bundle.webpackBuild()); 65 | 66 | test("cache file exits", async () => { 67 | expect(cacheFileExits()).toBeTruthy(); 68 | }); 69 | 70 | test("build file exits", async () => { 71 | bundle.copyAllFiles(); 72 | expect(await outputFileExits()).toBeTruthy(); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /tests/CacheController.test.ts: -------------------------------------------------------------------------------- 1 | import { CacheController, ManifestCache } from "../src/CacheController"; 2 | import { getDependencyFromYarn } from "../src/utils/packageDependency"; 3 | import * as path from "path"; 4 | import * as fse from "fs-extra"; 5 | 6 | const manifestPath = path.join(__dirname, "./manifest.json"); 7 | const cahceIndex = "index"; 8 | const entry = "chalk"; 9 | 10 | function genCache(): CacheController { 11 | return new CacheController({ 12 | configIndex: cahceIndex, 13 | manifestFile: manifestPath, 14 | entry 15 | }); 16 | } 17 | 18 | function createFile(manifest: ManifestCache) { 19 | return fse.writeJson(manifestPath, manifest).then(genCache); 20 | } 21 | 22 | function removeFile() { 23 | return fse.remove(manifestPath); 24 | } 25 | 26 | describe("empty manifest content", () => { 27 | let cache: CacheController; 28 | beforeAll(() => createFile({ configFiles: {} }).then(c => (cache = c))); 29 | afterAll(removeFile); 30 | 31 | test("should update", () => { 32 | expect(cache.shouldUpdateCache()).toBe(true); 33 | }); 34 | 35 | test("empty cache", () => { 36 | expect(cache.getCacheJSNames()).toHaveLength(0); 37 | }); 38 | 39 | test("should have cache", () => { 40 | const cacheJSNames = ["a", "b"]; 41 | cache.updateJSNamesCache(cacheJSNames); 42 | expect(cache.getCacheJSNames()).toEqual(cacheJSNames); 43 | }); 44 | }); 45 | 46 | describe("valid cache", () => { 47 | let cache: CacheController; 48 | beforeAll(() => 49 | createFile({ 50 | configFiles: { 51 | [cahceIndex]: { 52 | outputJSNames: [].concat(entry), 53 | entryVersion: getDependencyFromYarn(entry) 54 | } 55 | } 56 | }).then(c => (cache = c))); 57 | afterAll(removeFile); 58 | 59 | test("no need to update", () => { 60 | expect(cache.shouldUpdateCache()).toBeFalsy(); 61 | }); 62 | 63 | test("should have cache", () => { 64 | expect(cache.getCacheJSNames()).toEqual([].concat(entry)); 65 | }); 66 | }); 67 | 68 | describe("invalid cache", () => { 69 | let cache: CacheController; 70 | beforeAll(() => { 71 | const entryVersion = getDependencyFromYarn(entry); 72 | Object.keys(entryVersion).forEach(k => { 73 | entryVersion[k].version = `0.0.0`; 74 | }); 75 | 76 | return createFile({ 77 | configFiles: { 78 | [cahceIndex]: { 79 | outputJSNames: [].concat(entry), 80 | entryVersion 81 | } 82 | } 83 | }).then(c => (cache = c)); 84 | }); 85 | afterAll(removeFile); 86 | 87 | test("need to update", () => { 88 | expect(cache.shouldUpdateCache()).toBeTruthy(); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /tests/utils/packageDependency.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getPKGVersion, 3 | getDependencyFromYarn 4 | } from "../../src/utils/packageDependency"; 5 | 6 | test("utils - getPKGVersion", () => { 7 | const v = getPKGVersion("jest"); 8 | expect(v).toBe("22.4.3"); 9 | }); 10 | 11 | test("utils - getPKGVersion for namespaced package", () => { 12 | const v = getPKGVersion("@sindresorhus/is"); 13 | expect(v).toBe("0.7.0"); 14 | }); 15 | 16 | test("utils - getDependencyFromYarn", () => { 17 | const dep = getDependencyFromYarn("fs-extra"); 18 | expect(Object.keys(dep).length).toBeGreaterThan(0); 19 | }); 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "lib": ["es6"], 6 | "noImplicitAny": false, 7 | "sourceMap": false, 8 | "declaration": true, 9 | "outDir": "dist", 10 | "noEmitOnError": true, 11 | "moduleResolution": "node", 12 | "skipLibCheck": true, 13 | "noUnusedLocals": true 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [true, "check-space"], 5 | "indent": [true, "tab"], 6 | "linebreak-style": [true, "LF"], 7 | "one-line": [true, "check-open-brace", "check-whitespace"], 8 | "no-var-keyword": true, 9 | "quotemark": [true, "double", "avoid-escape"], 10 | "semicolon": [true, "always"], 11 | "whitespace": [ 12 | true, 13 | "check-branch", 14 | "check-decl", 15 | "check-operator", 16 | "check-module", 17 | "check-separator", 18 | "check-type" 19 | ], 20 | "typedef-whitespace": [ 21 | true, 22 | { 23 | "call-signature": "nospace", 24 | "index-signature": "nospace", 25 | "parameter": "nospace", 26 | "property-declaration": "nospace", 27 | "variable-declaration": "nospace" 28 | } 29 | ], 30 | "no-internal-module": true, 31 | "no-trailing-whitespace": true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /why-use-dll-link.md: -------------------------------------------------------------------------------- 1 | When using `DllReferencePlugin`,you will have to indicate the manifest file or context. 2 | 3 | ```js 4 | var webpack = require('webpack'); 5 | 6 | module.exports = { 7 | // ... 8 | plugins: [ 9 | new webpack.DllReferencePlugin({ 10 | context: '.', 11 | manifest: require('xxxx-manifest.json'), 12 | }) 13 | ] 14 | }; 15 | ``` 16 | 17 | And then compile manually. 18 | 19 | ``` 20 | $ webpack --config webpack.dll.config.js 21 | $ webpack --config webpack.config.js 22 | ``` 23 | 24 | When vendors change, compile manually again... 25 | 26 | ``` 27 | $ webpack --config webpack.dll.config.js 28 | $ webpack --config webpack.config.js 29 | ``` 30 | 31 | Let's see how things are different with `DllLinkPlugin`. 32 | 33 | The config file changes to 34 | 35 | ```js 36 | var DllLinkPlugin = require('dll-link-webpack-plugin'); 37 | 38 | module.exports = { 39 | // ... 40 | plugins: [ 41 | new DllLinkPlugin({ 42 | config: require('webpack.dll.config.js') 43 | }) 44 | ] 45 | } 46 | ``` 47 | 48 | No `DllReferencePlugin` any more, then compile. 49 | 50 | ```js 51 | $ webpack --config webpack.config.js 52 | ``` 53 | 54 | That's it! What you have to do is require the normal dll config file. Every time you run the above command, it will help you to detect the change and rebuild the vendors file automatically. --------------------------------------------------------------------------------