├── .gitignore
├── README.md
├── _example
├── html-css
│ ├── app
│ │ └── index.jsx
│ ├── assets
│ │ ├── css
│ │ │ ├── ie10.css
│ │ │ ├── ie8.css
│ │ │ └── ie9.css
│ │ ├── images
│ │ │ └── hi.png
│ │ └── index.html
│ ├── package.json
│ └── webpack.config.js
└── stylus-jade
│ ├── images
│ └── hi.png
│ ├── index.jade
│ ├── package.json
│ ├── scripts
│ └── index.js
│ ├── styles
│ └── index.styl
│ └── webpack.config.js
├── es6
└── index.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /index.js
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Webpack](http://webpack.github.io) plugin that:
2 |
3 | * writes text resources to the file system;
4 | * in these resources, replaces selected paths with their public counterparts.
5 |
6 | Plays nicely with [webpack-dev-server](http://webpack.github.io/docs/webpack-dev-server.html);
7 | see `includeHash` option of [the constructor](#new-pathrewriteropts--undefined).
8 |
9 |
10 | ## Example
11 |
12 | index.jade:
13 |
14 | ```jade
15 | doctype html
16 | head
17 | title My awesome app
18 | meta( charset="utf-8" )
19 | link( href="[[ app-*.css ]]", media="all", rel="stylesheet" )
20 | body
21 | p Hi everyone!
22 | img( src="[[ images/hi.png ]]" )
23 | script( src="[[ app-*.js ]]" )
24 | ```
25 |
26 | webpack.config.js:
27 |
28 | ```js
29 | var ExtractTextPlugin = require('extract-text-webpack-plugin'),
30 | PathRewriterPlugin = require('webpack-path-rewriter')
31 |
32 | module.exports = {
33 | entry: {
34 | app: './scripts/index'
35 | },
36 | output: {
37 | path: '_dist',
38 | filename: 'app-[chunkhash].js',
39 | publicPath: '/public/path/'
40 | },
41 | module: {
42 | loaders: [{
43 | test: /[/]images[/]/,
44 | loader: 'file?name=[path][name]-[hash].[ext]'
45 | }, {
46 | test: /[.]styl$/,
47 | loader: ExtractTextPlugin.extract('css?sourceMap!stylus?sourceMap')
48 | }, {
49 | test: /[.]jade$/,
50 | loader: PathRewriterPlugin.rewriteAndEmit({
51 | name: '[path][name].html',
52 | loader: 'jade-html?' + JSON.stringify({ pretty: true })
53 | })
54 | }]
55 | },
56 | plugins: [
57 | new ExtractTextPlugin('app-[contenthash].css', { allChunks: true }),
58 | new PathRewriterPlugin()
59 | ]
60 | }
61 | ```
62 |
63 | After the build, `_dist/index.html` will contain the following:
64 |
65 | ```html
66 |
67 |
68 | My awesome app
69 |
70 |
71 |
72 |
73 | Hi everyone!
74 |
75 |
76 |
77 | ```
78 |
79 |
80 | ## Usage
81 |
82 | This plugin is content-agnostic, so it doesn't perform any parsing. You need to explicitly mark
83 | each path that needs to be rewritten.
84 |
85 | By default, you do this by wrapping such paths inside the `"[[original/path]]"` construction, which
86 | after rewriting transforms to the `"rewritten/path"`. You can control this behavior using options,
87 | both global and per-resource.
88 |
89 | There are two types of assets.
90 |
91 | ### 1. Normal assets
92 |
93 | Most of the assets already exist in the source tree before you run the build. To rewrite paths to
94 | such assets, just mark these paths with the special construction described above.
95 |
96 | For example, if you have `views/index.jade` and `images/hi.png`, in the index.jade you can specify
97 | `"[[../images/hi.png]]"` and it will be replaced with whatever public path `images/hi.png` is
98 | assigned to, e.g. `"/static/images/hi-somelonghash.png"`.
99 |
100 | ### 2. Generated assets
101 |
102 | Some of the assets appear as a result of the build. These include JavaScript bundles (chunks)
103 | and files produced by
104 | [extract-text-webpack-plugin](https://github.com/webpack/extract-text-webpack-plugin).
105 | To rewrite path to an asset of this kind, you need to replace any variable parts of this path
106 | with asterisk `*` symbols.
107 |
108 | For example, to rewrite path from `views/index.jade` to the JS bundle which gets placed to
109 | `[hash]/scripts/app-[chunkhash].js`, the path should be specified as `"[[../*/scripts/app-*.js]]"`.
110 |
111 |
112 | ## Customizing the path marker
113 |
114 | Sometimes it may be inconvenient to use the default `"[[...]]"` marker. It can be customized using
115 | three options: `pathRegExp`, `pathMatchIndex` (index of capturing group containing extracted path)
116 | and `pathReplacer` (template of the replacement string).
117 |
118 | For example, you can use the following options to rewrite all `src` and `href` HTML attributes that
119 | end with some extension (non-relative paths are automatically skipped):
120 |
121 | ```js
122 | {
123 | pathRegExp: /(src|href)\s*=\s*"(.*?\.[\w\d]{1,6})"/,
124 | pathMatchIndex: 2,
125 | pathReplacer: '[1]="[path]"'
126 | }
127 | ```
128 |
129 |
130 | ## API
131 |
132 | #### `PathRewriter.rewriteAndEmit(loader | opts)`
133 |
134 | Marks a resource for rewriting paths and emitting to the file system. Use it in conjunction with the `new PathRewriter()` in the plugins list.
135 |
136 | Takes one argument, which is either string or object. If string, then it specifies the resource's
137 | loader string along with the options, e.g.
138 |
139 | ```js
140 | PathRewriter.rewriteAndEmit('?name=[path][name]-[hash].[ext]')
141 | PathRewriter.rewriteAndEmit('jade-html?pretty')
142 | PathRewriter.rewriteAndEmit('?name=[path][name].[ext]!jade-html?pretty')
143 | ```
144 |
145 | Object form allows to pass the following options:
146 |
147 | * `loader` the resource's loader string.
148 | * `loaders` an array of loaders; mutually exclusive with the `loader` option.
149 | * `name` the path to the output file. Defaults to `"[path][name].[ext]"`. May contain the
150 | following tokens:
151 | - `[ext]` the extension of the resource;
152 | - `[name]` the basename of the resource;
153 | - `[path]` the path of the resource relative to the `context` option;
154 | - `[hash]` the hash of the resource's content;
155 | - `[:hash::]` see
156 | [loader-utils docs](https://github.com/webpack/loader-utils#interpolatename);
157 | - `[N]` content of N-th capturing group obtained from matching the resource's path
158 | against the `nameRegExp` option.
159 | * `nameRegExp, context` see `name`.
160 | * `publicPath` allows to override the global `output.publicPath` setting.
161 | * `pathRegExp, pathMatchIndex, pathReplacer` allow to override options for rewriting paths
162 | in this resource.
163 |
164 | For example:
165 |
166 | ```js
167 | PathRewriter.rewriteAndEmit({
168 | name: '[path][name]-[hash].html',
169 | loader: 'jade-html?pretty'
170 | })
171 | ```
172 |
173 | #### `new PathRewriter(opts | undefined)`
174 |
175 | A plugin that emits to the filesystem all resources that were marked with the
176 | `PathRewriter.rewriteAndEmit()` loader. Options:
177 |
178 | * `silent` don't print rewritten paths. Defaults to `false`.
179 | * `emitStats` write `stats.json` file. May be string specifying the file's name.
180 | Defaults to `true`.
181 | * `pathRegExp` regular expression for matching paths. Defaults to `/"\[\[(.*?)\]\]"/`, which tests
182 | for `"[[...]]"` constructions and captures the string between the braces.
183 | * `pathMatchIndex` the index of capturing group in the `pathRegExp` that corresponds to a path.
184 | Defaults to `1`.
185 | * `pathReplacer` template for replacing matched path with the rewritten one. Defaults to
186 | `"[path]"`. May contain following tokens:
187 | - `[path]` the rewritten path;
188 | - `[N]` the content of N-th capturing group of `pathRegExp`.
189 | * `includeHash` make compilation's hash dependent on contents of this resource. Useful with live
190 | reload, as it causes the app to reload each time this resources changes. Defaults to `false`.
191 |
--------------------------------------------------------------------------------
/_example/html-css/app/index.jsx:
--------------------------------------------------------------------------------
1 | require("../assets/index.html")
2 |
3 | require("../assets/css/ie8.css")
4 | require("../assets/css/ie9.css")
5 | require("../assets/css/ie10.css")
6 |
--------------------------------------------------------------------------------
/_example/html-css/assets/css/ie10.css:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | background-color: #eee;
4 | }
5 |
--------------------------------------------------------------------------------
/_example/html-css/assets/css/ie8.css:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | background-color: #bbb;
4 | }
5 |
--------------------------------------------------------------------------------
/_example/html-css/assets/css/ie9.css:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | background-color: #ccc;
4 | }
5 |
--------------------------------------------------------------------------------
/_example/html-css/assets/images/hi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skozin/webpack-path-rewriter/86816f35d998fb866d93873462872282ede3bd85/_example/html-css/assets/images/hi.png
--------------------------------------------------------------------------------
/_example/html-css/assets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/_example/html-css/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack-path-rewriter-example",
3 | "version": "0.0.1",
4 | "scripts": {
5 | "build": "webpack"
6 | },
7 | "devDependencies": {
8 | "babel-core": "^5.0.9",
9 | "babel-loader": "^5.0.0",
10 | "css-loader": "^0.9.1",
11 | "file-loader": "^0.8.1",
12 | "webpack": "^1.7.2",
13 | "webpack-path-rewriter": "^1.1.0"
14 | },
15 | "dependencies": {}
16 | }
17 |
--------------------------------------------------------------------------------
/_example/html-css/webpack.config.js:
--------------------------------------------------------------------------------
1 | var PathRewriterPlugin = require('webpack-path-rewriter')
2 |
3 | module.exports = {
4 | entry: {
5 | app: "./app/index.jsx"
6 | },
7 | output: {
8 | path: '_dist',
9 | filename: 'app-[chunkhash].js',
10 | publicPath: '/public/path/'
11 | },
12 | module: {
13 | loaders: [{
14 | test: /[.]jsx?$/,
15 | exclude: /node_modules/,
16 | loader: 'babel?optional=runtime'
17 | }, {
18 | test: /[/]images[/]/,
19 | loader: 'file?name=[path][name]-[hash].[ext]'
20 | }, {
21 | test: /[.]css$/,
22 | loader: 'file?name=[path][name]-[hash].[ext]'
23 | }, {
24 | test: /[.]html$/,
25 | loader: PathRewriterPlugin.rewriteAndEmit({
26 | name: '[name].html'
27 | })
28 | }]
29 | },
30 | plugins: [
31 | new PathRewriterPlugin()
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/_example/stylus-jade/images/hi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skozin/webpack-path-rewriter/86816f35d998fb866d93873462872282ede3bd85/_example/stylus-jade/images/hi.png
--------------------------------------------------------------------------------
/_example/stylus-jade/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | head
3 | title My awesome app
4 | meta( charset="utf-8" )
5 | link( href="[[ app-*.css ]]", media="all", rel="stylesheet" )
6 | body
7 | p Hi everyone!
8 | img( src="[[ ./images/hi.png ]]" )
9 | script( src="[[ app-*.js ]]" )
10 |
--------------------------------------------------------------------------------
/_example/stylus-jade/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack-path-rewriter-example",
3 | "version": "0.0.1",
4 | "scripts": {
5 | "build": "webpack"
6 | },
7 | "devDependencies": {
8 | "css-loader": "^0.9.1",
9 | "extract-text-webpack-plugin": "^0.3.8",
10 | "file-loader": "^0.8.1",
11 | "jade": "^1.9.2",
12 | "jade-html-loader": "bline/jade-html-loader",
13 | "stylus": "^0.49.3",
14 | "stylus-loader": "shama/stylus-loader",
15 | "webpack": "^1.7.2",
16 | "webpack-path-rewriter": "^1.1.2"
17 | },
18 | "dependencies": {}
19 | }
20 |
--------------------------------------------------------------------------------
/_example/stylus-jade/scripts/index.js:
--------------------------------------------------------------------------------
1 | require('../index.jade')
2 | require('../styles/index.styl')
3 |
4 | console.log('Hi everyone!')
5 |
--------------------------------------------------------------------------------
/_example/stylus-jade/styles/index.styl:
--------------------------------------------------------------------------------
1 |
2 | body
3 | background-color #ccc
4 |
--------------------------------------------------------------------------------
/_example/stylus-jade/webpack.config.js:
--------------------------------------------------------------------------------
1 | var ExtractTextPlugin = require('extract-text-webpack-plugin'),
2 | PathRewriterPlugin = require('webpack-path-rewriter')
3 |
4 | module.exports = {
5 | entry: {
6 | app: './scripts/index'
7 | },
8 | output: {
9 | path: '_dist',
10 | filename: 'app-[chunkhash].js',
11 | publicPath: '/public/path/'
12 | },
13 | module: {
14 | loaders: [{
15 | test: /[/]images[/]/,
16 | loader: 'file?name=[path][name]-[hash].[ext]'
17 | }, {
18 | test: /[.]styl$/,
19 | loader: ExtractTextPlugin.extract('css?sourceMap!stylus?sourceMap')
20 | }, {
21 | test: /[.]jade$/,
22 | loader: PathRewriterPlugin.rewriteAndEmit({
23 | name: '[path][name].html',
24 | loader: 'jade-html?' + JSON.stringify({ pretty: true })
25 | })
26 | }]
27 | },
28 | plugins: [
29 | new ExtractTextPlugin('app-[chunkhash].css', { allChunks: true }),
30 | new PathRewriterPlugin()
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/es6/index.js:
--------------------------------------------------------------------------------
1 | import loaderUtils from 'loader-utils'
2 | import path from 'path'
3 |
4 |
5 | const PATH_REGEXP = /"\[\[(.*?)\]\]"/g,
6 | PATH_MATCH_INDEX = 1,
7 | PATH_REPLACER = '"[path]"'
8 |
9 | const INLINE_REGEXP = /\[\[\s*INLINE\(([^)]*)\)\s*\]\]/g
10 |
11 | const ABS_PATH_REGEXP = /^[/]|^\w+:[/][/]/,
12 | HASH_REGEXP_SRC = '[\\w\\d_-]+[=]*'
13 |
14 |
15 | class PathRewriter
16 | {
17 | /**
18 | * Marks a resource for rewriting paths and emitting to the file system.
19 | * Use it in conjunction with the `new PathRewriter()` plugin.
20 | *
21 | * The `opts` argument is either string or object. If string, it specifies
22 | * the resource's loader string along with the options, e.g.
23 | *
24 | * PathRewriter.rewriteAndEmit('?name=[path][name]-[hash].[ext]')
25 | * PathRewriter.rewriteAndEmit('jade-html?pretty')
26 | * PathRewriter.rewriteAndEmit('?name=[path][name].[ext]!jade-html?pretty')
27 | *
28 | * Object form allows to specify the following options:
29 | *
30 | * - loader: the resource's loader string.
31 | *
32 | * - loaders: the array of loaders; mutually exclusive with the `loader` option.
33 | *
34 | * - name: the path to the output file, may contain the following tokens:
35 | *
36 | * - [ext] the extension of the resource;
37 | * - [name] the basename of the resource;
38 | * - [path] the path of the resource relative to the `context` option;
39 | * - [hash] the hash of the resource's content;
40 | * - [:hash::]
41 | * see https://github.com/webpack/loader-utils#interpolatename;
42 | * - [N] content of N-th capturing group obtained from matching
43 | * the resource's path against the `nameRegExp` option.
44 | *
45 | * Defaults to '[path][name].[ext]'.
46 | *
47 | * - nameRegExp, context: see `name`.
48 | *
49 | * - publicPath: allows to override the global output.publicPath setting.
50 | *
51 | * - pathRegExp, pathMatchIndex, pathReplacer: allow to override options for
52 | * rewriting paths in this resource. See documentation for the constructor.
53 | *
54 | * Example:
55 | *
56 | * PathRewriter.rewriteAndEmit({
57 | * name: '[path][name]-[hash].html',
58 | * loader: 'jade-html?pretty'
59 | * })
60 | */
61 | static rewriteAndEmit(rwOpts)
62 | {
63 | var thisLoader = module.filename,
64 | loader = ''
65 |
66 | if ('string' == typeof rwOpts) {
67 | return thisLoader + (/^[?!]/.test(rwOpts)
68 | ? rwOpts
69 | : rwOpts && '!' + rwOpts
70 | )
71 | }
72 |
73 | if (rwOpts.loader != undefined) {
74 | loader = rwOpts.loader
75 | }
76 |
77 | if (rwOpts.loaders != undefined) {
78 | if (rwOpts.loader != undefined) {
79 | throw new Error('cannot use both loader and loaders')
80 | }
81 | loader = rwOpts.loaders.join('!')
82 | }
83 |
84 | var query = extend({}, rwOpts, (key) => key != 'loader' && key != 'loaders')
85 |
86 | if (query.pathRegExp) {
87 | let re = query.pathRegExp; query.pathRegExp = re instanceof RegExp ? {
88 | source: re.source,
89 | flags: (re.ignoreCase ? 'i' : '') + (re.multiline ? 'm' : '')
90 | } : {
91 | source: '' + re,
92 | flags: ''
93 | }
94 | }
95 |
96 | if (query.inlineRegExp) {
97 | let re = query.inlineRegExp; query.inlineRegExp = re instanceof RegExp ? {
98 | source: re.source,
99 | flags: (re.ignoreCase ? 'i' : '') + (re.multiline ? 'm' : '')
100 | } : {
101 | source: '' + re,
102 | flags: ''
103 | }
104 | }
105 |
106 | // we need to temporarily replace all exclamation marks because they have
107 | // special meaning in loader strings v
108 | //
109 | return thisLoader + '?' + JSON.stringify(query).replace(/!/g, ':BANG:') + (/^!/.test(loader)
110 | ? loader
111 | : loader && '!' + loader
112 | )
113 | }
114 |
115 |
116 | /**
117 | * A plugin that emits to the filesystem all resources that are marked
118 | * with `PathRewriter.rewriteAndEmit()` loader.
119 | *
120 | * Options:
121 | *
122 | * - silent: don't print rewritten paths. Defaults to false.
123 | *
124 | * - emitStats: write stats.json file. May be string specifying
125 | * the file's name. Defaults to true.
126 | *
127 | * - pathRegExp: regular expression for matching paths. Defaults
128 | * to /"\[\[(.*?)\]\]"/, which tests for \"[[...]]\" constructions,
129 | * capturing the string between the braces.
130 | *
131 | * - pathMatchIndex: the index of capturing group in the pathRegExp
132 | * that corresponds to a path.
133 | *
134 | * - pathReplacer: template for replacing matched path with the
135 | * rewritten one. May contain following tokens:
136 | * - [path] the rewritten path;
137 | * - [N] the content of N-th capturing group of pathRegExp.
138 | * Defaults to \"[path]\".
139 | *
140 | * - includeHash: make compilation's hash dependent on contents
141 | * of this resource. Useful with live reload, as it causes the app
142 | * to reload each time this resources changes. Defaults to false.
143 | */
144 | constructor(opts)
145 | {
146 | this.opts = extend({
147 | silent: false,
148 | emitStats: true,
149 | includeHash: false,
150 | pathRegExp: undefined,
151 | pathReplacer: undefined,
152 | pathMatchIndex: undefined
153 | }, opts)
154 | this.pathRegExp = PathRewriter.makeRegExp(this.opts.pathRegExp) || PATH_REGEXP
155 | this.pathMatchIndex = this.opts.pathMatchIndex == undefined
156 | ? PATH_MATCH_INDEX
157 | : this.opts.pathMatchIndex
158 | this.pathReplacer = this.opts.pathReplacer || PATH_REPLACER
159 | this.inlineRegExp = PathRewriter.makeRegExp(this.opts.inlineRegExp) || INLINE_REGEXP
160 | this.rwPathsCache = {}
161 | this.modules = []
162 | this.modulesByRequest = {}
163 | this.compilationRwPathsCache = undefined
164 | this.compilationAssetsPaths = undefined
165 | }
166 |
167 |
168 | static loader(content) // loader entry point, called by Webpack; see PathRewriterEntry
169 | {
170 | this.cacheable && this.cacheable()
171 |
172 | var rewriter = this[ __dirname ]
173 | if (rewriter == undefined) {
174 | throw new Error(
175 | 'webpack-path-rewriter loader is used without the corresponding plugin;\n ' +
176 | 'add `new PathRewriter()` to the list of plugins in the Webpack config'
177 | )
178 | }
179 |
180 | var query = loaderUtils.parseQuery(this.query && this.query.replace(/:BANG:/g, '!')),
181 | topLevelContext = this.options.context,
182 | publicPath = query.publicPath || this.options.output.publicPath || ''
183 |
184 | if (publicPath.length && publicPath[ publicPath.length - 1 ] != '/') {
185 | publicPath = publicPath + '/'
186 | }
187 |
188 | var url = loaderUtils.interpolateName(this, query.name || '[path][name].[ext]', {
189 | content: content,
190 | context: query.context || topLevelContext,
191 | regExp: query.nameRegExp
192 | })
193 |
194 | var moduleData = {url, content, publicPath, topLevelContext,
195 | request: this.request,
196 | context: this.context,
197 | relPath: path.relative(topLevelContext, this.resourcePath),
198 | pathRegExp: PathRewriter.makeRegExp(query.pathRegExp) || rewriter.pathRegExp,
199 | pathReplacer: query.pathReplacer || rewriter.pathReplacer,
200 | pathMatchIndex: +(query.pathMatchIndex == undefined
201 | ? rewriter.pathMatchIndex
202 | : query.pathMatchIndex
203 | ),
204 | inlineRegExp: PathRewriter.makeRegExp(query.inlineRegExp) || rewriter.inlineRegExp
205 | }
206 |
207 | var exportStatement = 'module.exports = "' + publicPath + url + (rewriter.opts.includeHash
208 | ? '" // content hash: ' + loaderUtils.interpolateName(this, '[hash]', { content })
209 | : '"'
210 | )
211 |
212 | var callback = this.async(); PathRewriter.extractAssets(this, moduleData, assetRequests =>
213 | {
214 | rewriter.addModule(moduleData)
215 |
216 | callback(null, assetRequests
217 | .map(req => `require(${ JSON.stringify(req) })`)
218 | .join('\n') + '\n' + exportStatement
219 | )
220 | })
221 | }
222 |
223 |
224 | static makeRegExp(desc)
225 | {
226 | if (desc == undefined) {
227 | return undefined
228 | }
229 | if (desc instanceof RegExp) {
230 | return new RegExp(desc.source, 'g'
231 | + (desc.ignoreCase ? 'i' : '')
232 | + (desc.multiline ? 'm' : '')
233 | )
234 | }
235 | var src = desc.source || '' + desc,
236 | flags = desc.flags || 'g'
237 | return new RegExp(src, flags.indexOf('g') == -1
238 | ? flags + 'g'
239 | : flags
240 | )
241 | }
242 |
243 |
244 | static extractAssets(loaderCtx, moduleData, cb)
245 | {
246 | var paths = PathRewriter.findNonWildcardPaths(moduleData),
247 | numPaths = paths.length,
248 | numPathsDone = 0,
249 | assetsData = []
250 | if (paths.length == 0) {
251 | return done(assetsData)
252 | }
253 | paths.forEach(path => {
254 | var request = loaderUtils.urlToRequest(path)
255 | // we need to discard all possibly generated assets, i.e. those
256 | // that are not present in the source tree, because we cannot
257 | // require them
258 | loaderCtx.resolve(moduleData.context, request, (err, _) => {
259 | if (err == null) {
260 | assetsData.push({ path, request, rwPath: undefined })
261 | }
262 | if (++numPathsDone == numPaths) {
263 | done(assetsData)
264 | }
265 | })
266 | })
267 | function done(assetsData) {
268 | var byPath = {}, byRequest = {}
269 | assetsData.forEach(data => {
270 | byRequest[ data.request ] = data
271 | byPath[ data.path ] = data
272 | })
273 | moduleData.assetDataByPath = byPath
274 | moduleData.assetDataByRequest = byRequest
275 | cb(assetsData.map(data => data.request))
276 | }
277 | }
278 |
279 |
280 | static findNonWildcardPaths({ content, pathRegExp, pathMatchIndex })
281 | {
282 | var results = [],
283 | matches
284 | while (matches = pathRegExp.exec(content)) {
285 | var path = trim(matches[ pathMatchIndex ])
286 | if (path
287 | && path.indexOf('*') == -1
288 | && results.indexOf(path) == -1
289 | && loaderUtils.isUrlRequest(path)
290 | ){
291 | results.push(path)
292 | }
293 | }
294 | pathRegExp.lastIndex = 0
295 | return results
296 | }
297 |
298 |
299 | addModule(moduleData)
300 | {
301 | this.modules.push(moduleData)
302 | this.modulesByRequest[ moduleData.request ] = moduleData
303 | }
304 |
305 |
306 | apply(compiler) // plugin entry point, called by Webpack
307 | {
308 | compiler.plugin('compilation', (compilation) => {
309 | compilation.plugin('normal-module-loader', (loaderContext, module) => {
310 | if (loaderContext[ __dirname ])
311 | throw new Error('cannot use more than one instance of PathRewriter in the plugins list')
312 | loaderContext[ __dirname ] = this
313 | })
314 | })
315 | compiler.plugin('after-compile', (compilation, callback) => {
316 | this.onAfterCompile(compilation, callback)
317 | })
318 | compiler.plugin('emit', (compiler, callback) => {
319 | this.onEmit(compiler, callback)
320 | })
321 | }
322 |
323 |
324 | onAfterCompile(compilation, callback)
325 | {
326 | compilation.modules.forEach(module => {
327 | var moduleData = this.modulesByRequest[ module.request ]
328 | moduleData && this.extractModuleAssetsPublicPaths(moduleData, module)
329 | })
330 | callback()
331 | }
332 |
333 |
334 | extractModuleAssetsPublicPaths(moduleData, module)
335 | {
336 | var assetDataByRequest = moduleData.assetDataByRequest,
337 | deps = module.dependencies
338 |
339 | for (var i = 0; i < deps.length; ++i) {
340 | var dep = deps[i]
341 | if (!dep.request || !dep.module) continue
342 |
343 | var assetData = assetDataByRequest[ dep.request ]
344 | if (assetData == undefined) continue
345 |
346 | var assets = dep.module.assets && Object.keys(dep.module.assets) || []
347 | if (assets.length != 1) {
348 | let paths = assets.map(a => `"${a}"`).join(', ')
349 | assetData.error = new PathRewriterError(
350 | `invalid number of assets for path "${ assetData.path }", assets: [${ paths }]`,
351 | moduleData
352 | )
353 | continue
354 | }
355 |
356 | assetData.rwPath = assets[0]
357 | }
358 | }
359 |
360 |
361 | onEmit(compiler, callback)
362 | {
363 | var stats = compiler.getStats().toJson()
364 |
365 | this.compilationAssetsPaths = stats.assets.map(asset => asset.name)
366 | this.compilationRwPathsCache = {}
367 |
368 | this.modules.forEach(moduleData => {
369 | this.rewriteModulePaths(moduleData, compiler)
370 | })
371 |
372 | this.modules = []
373 | this.modulesByRequest = {}
374 |
375 | // WARN WTF
376 | //
377 | // We need to cache assets from previous compilations in watch mode
378 | // because, for some reason, some assets don't get included in the
379 | // assets list after recompilations.
380 | //
381 | extend(this.rwPathsCache, this.compilationRwPathsCache)
382 |
383 | if (this.opts.emitStats) {
384 | var statsJson = JSON.stringify(stats, null, ' '),
385 | statsPath = typeof this.opts.emitStats == 'string'
386 | ? this.opts.emitStats
387 | : 'stats.json'
388 | compiler.assets[ statsPath ] = {
389 | source: () => statsJson,
390 | size: () => statsJson.length
391 | }
392 | }
393 |
394 | callback()
395 | }
396 |
397 |
398 | rewriteModulePaths(moduleData, compiler)
399 | {
400 | var content = moduleData.content.replace(moduleData.pathRegExp, (...matches) => {
401 | var srcPath = trim(matches[ moduleData.pathMatchIndex ])
402 | try {
403 | var rwPath = this.rewritePath(srcPath, moduleData)
404 | rwPath = this.prependPublicPath(moduleData.publicPath, rwPath)
405 | this.opts.silent || (srcPath != rwPath) && console.error(
406 | `PathRewriter[ ${ moduleData.relPath } ]: "${ srcPath }" -> "${ rwPath }"`
407 | )
408 | return moduleData.pathReplacer.replace(/\[(path|\d+)\]/g, (_, t) => {
409 | return t == 'path' ? rwPath : matches[ +t ]
410 | })
411 | }
412 | catch(e) {
413 | if (!(e instanceof PathRewriterError) && !this.opts.silent) {
414 | console.error(e.stack)
415 | }
416 | compiler.errors.push(e)
417 | return srcPath
418 | }
419 | }).replace(moduleData.inlineRegExp, (match, assetUrl) => {
420 | let asset = compiler.assets[ assetUrl ]
421 | if (!asset) {
422 | compiler.errors.push(new Error(
423 | `Cannot inline asset "${ assetUrl }" in ${ moduleData.relPath }: not found`
424 | ))
425 | return match
426 | }
427 | else if (!this.opts.silent) {
428 | console.error(`PathRewriter[ ${ moduleData.relPath } ]: inlined "${ assetUrl }"`)
429 | }
430 | return asset.source()
431 | })
432 | compiler.assets[ moduleData.url ] = {
433 | source: () => content,
434 | size: () => content.length
435 | }
436 | }
437 |
438 |
439 | rewritePath(srcPath, moduleData)
440 | {
441 | var key = moduleData.context + '|' + srcPath,
442 | rwPath = this.compilationRwPathsCache[ key ]
443 |
444 | if (rwPath)
445 | return rwPath
446 |
447 | var assetData = moduleData.assetDataByPath[ srcPath ]
448 | if (assetData) {
449 | if (assetData.error)
450 | throw assetData.error
451 | rwPath = assetData.rwPath
452 | }
453 | else {
454 | rwPath = this.rewriteGeneratedAssetPath(srcPath, moduleData)
455 | }
456 |
457 | if (rwPath == undefined) {
458 | // in watch mode, sometimes some assets are not listed during
459 | // recompilations, so we need to use a long-term cache
460 | rwPath = this.rwPathsCache[ key ]
461 | }
462 |
463 | if (rwPath == undefined || rwPath.length == 0) {
464 | throw new PathRewriterError(`could not resolve path "${ srcPath }"`, moduleData)
465 | }
466 |
467 | this.compilationRwPathsCache[ key ] = rwPath
468 | return rwPath
469 | }
470 |
471 |
472 | rewriteGeneratedAssetPath(srcPath, moduleData)
473 | {
474 | if (ABS_PATH_REGEXP.test(srcPath))
475 | return srcPath
476 |
477 | var parts = srcPath.split(/[*]+/)
478 |
479 | if (parts.join('').length == 0) {
480 | throw new PathRewriterError(
481 | `invalid wildcard path "${ srcPath }", must contain at least one non-wildcard symbol`,
482 | moduleData
483 | )
484 | }
485 |
486 | var searchRE = new RegExp('^' + parts.map(escapeRegExp).join(HASH_REGEXP_SRC) + '$')
487 |
488 | for (var i = 0; i < this.compilationAssetsPaths.length; ++i) {
489 | var rwPath = this.compilationAssetsPaths[i]
490 | if (searchRE.test(rwPath)) {
491 | return rwPath
492 | }
493 | }
494 |
495 | return undefined
496 | }
497 |
498 |
499 | prependPublicPath(publicPath, path)
500 | {
501 | return ABS_PATH_REGEXP.test(path)
502 | ? path
503 | : publicPath + path
504 | }
505 | }
506 |
507 |
508 | function trim(s) {
509 | return s && s.replace(/^\s+|\s+$/g, '')
510 | }
511 |
512 |
513 | function escapeRegExp(s) {
514 | return s && s.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&')
515 | }
516 |
517 |
518 | function extend(dst, src, filter) {
519 | if (src) Object.keys(src).forEach(filter == undefined
520 | ? key => { dst[ key ] = src[ key ] }
521 | : key => {
522 | if (filter(key)) {
523 | dst[ key ] = src[ key ]
524 | }
525 | }
526 | )
527 | return dst
528 | }
529 |
530 |
531 | function PathRewriterError(msg, moduleData)
532 | {
533 | Error.call(this)
534 | Error.captureStackTrace(this, PathRewriterError)
535 | this.name = 'PathRewriterError'
536 | this.message = moduleData.relPath + ': ' + msg
537 | }
538 | PathRewriterError.prototype = Object.create(Error.prototype)
539 |
540 |
541 | function PathRewriterEntry(arg)
542 | {
543 | return this instanceof PathRewriterEntry
544 | ? new PathRewriter(arg) // called with new => plugin
545 | : PathRewriter.loader.call(this, arg) // called as a funtion => loader
546 | }
547 | PathRewriterEntry.rewriteAndEmit = PathRewriter.rewriteAndEmit
548 |
549 |
550 | module.exports = PathRewriterEntry
551 | export default PathRewriterEntry
552 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack-path-rewriter",
3 | "version": "1.1.4",
4 | "description": "Webpack plugin for replacing resources' paths with public ones",
5 | "author": "Sam Kozin ",
6 | "scripts": {
7 | "prepublish": "babel ./es6 --presets es2015 --out-dir .",
8 | "watch": "babel --watch ./es6 --presets es2015 --out-dir .",
9 | "test": "echo 'Sorry, no tests yet' && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/skozin/webpack-path-rewriter.git"
14 | },
15 | "keywords": [
16 | "webpack",
17 | "plugin",
18 | "assets",
19 | "hashing",
20 | "html"
21 | ],
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/skozin/webpack-path-rewriter/issues"
25 | },
26 | "homepage": "https://github.com/skozin/webpack-path-rewriter",
27 | "dependencies": {
28 | "loader-utils": "^0.2.11"
29 | },
30 | "devDependencies": {
31 | "babel-cli": "^6.1.18",
32 | "babel-preset-es2015": "^6.1.18"
33 | },
34 | "files": [
35 | "index.js",
36 | "es6/index.js"
37 | ],
38 | "main": "index.js"
39 | }
40 |
--------------------------------------------------------------------------------