Skip to content

Commit 086e3fb

Browse files
committed
initial
0 parents  commit 086e3fb

File tree

12 files changed

+400
-0
lines changed

12 files changed

+400
-0
lines changed

.editorconfig

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# http://editorconfig.org
2+
root = true
3+
4+
[*]
5+
indent_style = space
6+
indent_size = 2
7+
end_of_line = lf
8+
charset = utf-8
9+
trim_trailing_whitespace = true
10+
insert_final_newline = true
11+
12+
[*.md]
13+
trim_trailing_whitespace = false
14+
15+
[Makefile]
16+
indent_style = tab
17+
18+
[package.json]
19+
insert_final_newline = false

.gitignore

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.*
2+
**/.DS_Store
3+
*.log
4+
!.editorconfig
5+
!.npmignore
6+
!.gitignore
7+
!.build
8+
!.*.yml
9+
10+
/coverage/
11+
/node_modules/
12+
/.idea/
13+
/test/fixtures/*/dist

.npmignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.DS_Store
2+
*.log
3+
4+
/demo/
5+
/coverage/
6+
/test/
7+
/node_modules/

HISTORY.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
1.0.0 / 2018-06-04
3+
==================
4+
5+
* init
6+

README.md

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# css-url-relative-plugin
2+
3+
Webpack plugin to convert css url(...) to relative path (only support webpack 4).
4+
5+
This plugin aim to solve the problem that webpack generate incorrect relative path when your publicPath is empty (defaults to `''`) or `'./'`, it will replace incorrect path in css `url(...)`s with correct relative path at end of webpack compilation process.
6+
7+
For example:
8+
9+
```css
10+
/*
11+
* /project
12+
* |- dist
13+
* | |- xxx.hash.png
14+
* | |- page
15+
* | |- index.hash.css
16+
* |- src
17+
* |- img
18+
* | |- xxx.png
19+
* |- page
20+
* |- index.css
21+
*/
22+
/* page/index.css (original css code you write) */
23+
body {
24+
background: url(../img/xxx.png)
25+
}
26+
27+
/* page/index.hash.css (webpack generated) */
28+
body {
29+
/*
30+
* css-url-relative-plugin will generate: url(../xxx.hash.png)
31+
*/
32+
background: url(xxx.hash.png)
33+
}
34+
```
35+
36+
As you can see, the image path in `url(...)` is relative to output dir, not the css file.
37+
38+
## Usage
39+
40+
```js
41+
const CssUrlRelativePlugin = require('css-url-relative-plugin')
42+
43+
module.exports = {
44+
...
45+
plugins: [
46+
new CssUrlRelativePlugin(/* options */)
47+
]
48+
}
49+
```
50+
51+
## Options
52+
53+
### root
54+
55+
Like `root` option in [css-loader](https://webpack.js.org/loaders/css-loader/#root), it's the path to resolve URLs.
56+
57+
58+
## LICENSE
59+
60+
MIT

lib/css-replace.js

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author yibn2008<yibn2008@qq.com>
4+
*/
5+
'use strict'
6+
7+
const parseImport = require('parse-import')
8+
9+
const COMMENT_RULE = /\/\*([\s\S]*?)\*\//g
10+
const RESOLVE_RULE = /@import\s+[^;]*;|url\(([^)]*)\)/g
11+
12+
/**
13+
* resolve @import and url(...)
14+
*
15+
* @param {String} content
16+
* @param {Function} replacer
17+
*/
18+
function cssReplace (content, replacer) {
19+
// replace all comments to comment mark /*{ID}*/
20+
const comments = []
21+
content = content.replace(COMMENT_RULE, (comment) => {
22+
const id = comments.length
23+
comments.push(comment)
24+
return `/*${id}*/`
25+
})
26+
27+
// replace @import with replacer
28+
content = content.replace(RESOLVE_RULE, (statement, urlPath) => {
29+
if (statement.startsWith('@import')) {
30+
// remove possible comments
31+
statement = statement.replace(COMMENT_RULE, '')
32+
33+
const parsed = parseImport(statement)
34+
if (!parsed.length) {
35+
throw new Error(`parse rule ${statement} failed`)
36+
}
37+
38+
/**
39+
* parsed[0]: {
40+
* path: 'foobar.css',
41+
* condition: 'print',
42+
* rule: '@import url("foobar.css") print'
43+
* }
44+
*/
45+
return replacer(Object.assign({
46+
type: 'import'
47+
}, parsed[0]))
48+
} else {
49+
// remove possible comments
50+
urlPath = urlPath.replace(COMMENT_RULE, '').trim()
51+
52+
const clearPath = urlPath.replace(/['"]/g, '')
53+
54+
return replacer({
55+
type: 'url',
56+
path: clearPath,
57+
condition: '',
58+
rule: `url(${urlPath})`
59+
})
60+
}
61+
})
62+
63+
// replace back from comment mark to real comment
64+
content = content.replace(COMMENT_RULE, (comment, id) => {
65+
return comments[id | 0]
66+
})
67+
68+
return content
69+
}
70+
71+
module.exports = cssReplace

lib/index.js

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author yibn2008<yibn2008@qq.com>
4+
*/
5+
'use strict'
6+
7+
// const chalk = require('chalk')
8+
const path = require('path')
9+
const { RawSource } = require('webpack-sources')
10+
const loaderUtils = require('loader-utils')
11+
const cssReplace = require('./css-replace')
12+
13+
const isCSS = (name) => /\.css$/.test(name)
14+
// const strip = (str) => str.replace(/\/$/, '')
15+
16+
class CssUrlRelativePlugin {
17+
18+
constructor (options) {
19+
this.options = options || {}
20+
}
21+
22+
fixCssUrl (compilation, chunks, done) {
23+
const root = this.options.root
24+
const assets = compilation.assets
25+
const publicPath = compilation.options.output.publicPath || ''
26+
27+
chunks.map((chunk) => {
28+
const input = chunk.files.filter(isCSS)
29+
30+
for (let name of input) {
31+
const asset = assets[name]
32+
const dirname = path.dirname(name)
33+
let source = asset.source()
34+
35+
// replace url to relative
36+
source = cssReplace(source, refer => {
37+
// handle url(...)
38+
if (refer.type === 'url' && loaderUtils.isUrlRequest(refer.path, root)) {
39+
// remove publicPath parts
40+
let pathname = refer.path
41+
if (publicPath && pathname.startsWith(publicPath)) {
42+
pathname = pathname.substring(publicPath.length)
43+
}
44+
45+
// get relative path
46+
pathname = path.relative(dirname, pathname).replace(/\\/g, '/')
47+
48+
return `url(${pathname})`
49+
}
50+
51+
// return original rule
52+
return refer.rule
53+
})
54+
55+
assets[name] = new RawSource(source)
56+
}
57+
})
58+
59+
done()
60+
}
61+
62+
apply (compiler) {
63+
const plugin = {
64+
name: 'CssUrlRelativePlugin'
65+
}
66+
67+
// use compilation instead of this-compilation, just like other plugins do
68+
compiler.hooks.compilation.tap(plugin, compilation => {
69+
compilation.hooks.optimizeChunkAssets.tapAsync(plugin, (chunks, done) => {
70+
this.fixCssUrl(compilation, chunks, done)
71+
})
72+
})
73+
}
74+
}
75+
76+
module.exports = CssUrlRelativePlugin

package.json

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"name": "css-url-relative-plugin",
3+
"version": "1.0.0",
4+
"description": "webpack plugin to convert url(...) in css to relative path",
5+
"main": "lib/index.js",
6+
"scripts": {
7+
"lint": "standard --fix && npm-ensure -t deps",
8+
"test": "npm run lint && npm run test-local",
9+
"test-local": "mocha test/**/*.test.js",
10+
"test-cov": "nyc npm run test-local",
11+
"ci": "npm run lint && npm run test-cov",
12+
"precommit": "standard && npm-ensure -t deps"
13+
},
14+
"ensure": {
15+
"deps": {
16+
"checkDirs": [
17+
"lib/**/*",
18+
"bin/*"
19+
]
20+
}
21+
},
22+
"nyc": {
23+
"reporter": [
24+
"text",
25+
"json",
26+
"lcov"
27+
]
28+
},
29+
"standard": {
30+
"global": [
31+
"describe",
32+
"it",
33+
"beforeEach",
34+
"afterEach"
35+
],
36+
"ignore": [
37+
"/test"
38+
]
39+
},
40+
"dependencies": {
41+
"loader-utils": "^1.1.0",
42+
"parse-import": "^2.0.0",
43+
"webpack-sources": "^1.1.0"
44+
},
45+
"devDependencies": {
46+
"css-loader": "^0.28.11",
47+
"file-loader": "^1.1.11",
48+
"husky": "0.x",
49+
"mini-css-extract-plugin": "^0.4.0",
50+
"mocha": "^3.0.2",
51+
"npm-ensure": "^1.0.0",
52+
"nyc": "11.x",
53+
"standard": "^8.2.0",
54+
"webpack": "^4.10.2"
55+
},
56+
"repository": {
57+
"type": "git",
58+
"url": "git@github.com:yibn2008/css-url-relative-plugin.git"
59+
},
60+
"keywords": [
61+
"css-url-relative-plugin"
62+
],
63+
"author": "zoujie.wzj",
64+
"license": "MIT"
65+
}

test/fixtures/simple/src/img/xxx.png

+1
Loading
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
body {
2+
background: url(../img/xxx.png);
3+
}
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict'
2+
3+
const path = require('path')
4+
const webpack = require('webpack')
5+
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
6+
const CssUrlRelativePlugin = require('../../..')
7+
8+
module.exports = {
9+
mode: 'development',
10+
devtool: false,
11+
entry: {
12+
'page/index': path.join(__dirname, 'src/page/index.css')
13+
},
14+
output: {
15+
path: path.join(__dirname, 'dist')
16+
},
17+
module: {
18+
rules: [
19+
{
20+
test: /\.png$/,
21+
loader: 'file-loader',
22+
options: {
23+
name: '[name].[ext]'
24+
}
25+
},
26+
{
27+
test: /\.css$/,
28+
use: [
29+
MiniCssExtractPlugin.loader,
30+
'css-loader'
31+
]
32+
}
33+
]
34+
},
35+
plugins: [
36+
new MiniCssExtractPlugin({
37+
name: '[name].css'
38+
}),
39+
new CssUrlRelativePlugin()
40+
]
41+
}

0 commit comments

Comments
 (0)