├── .editorconfig
├── .eslintrc.js
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .npmrc
├── .releaserc.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── Taskfile
├── gulpfile.js
├── index.js
├── package.json
├── prettier.config.cjs
└── test
├── fixture-1
├── a.css
├── a1.css
├── main.html
└── style.css
├── fixture-2
├── a.css
├── a1.css
├── b.css
├── b1.css
├── main.html
└── style.css
├── fixture-3
├── a.css
├── main.html
├── recursive
│ └── b.css
└── style.css
└── test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | browser: true,
6 | mocha: true,
7 | es6: true,
8 | },
9 | parserOptions: {
10 | ecmaVersion: 2020,
11 | },
12 | extends: ['eslint:recommended'],
13 | plugins: [],
14 | overrides: [
15 | {
16 | files: ['test/**/*.js'],
17 | rules: {
18 | 'max-lines': 0,
19 | },
20 | },
21 | ],
22 | };
23 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: 'Test / Build / Release'
2 | on:
3 | - push
4 | - pull_request
5 |
6 | jobs:
7 | test:
8 | name: 'Test'
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: 'Checkout repository'
12 | uses: actions/checkout@v3
13 | - name: 'Setup node'
14 | uses: actions/setup-node@v3
15 | with:
16 | node-version: 18
17 | - name: 'Install depependencies'
18 | run: npm install
19 | - name: 'Test'
20 | run: |
21 | npm run test
22 |
23 | release:
24 | name: 'Release'
25 | runs-on: ubuntu-latest
26 | needs: test
27 | if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/next')
28 | steps:
29 | - name: 'Checkout repository'
30 | uses: actions/checkout@v3
31 | - name: 'Setup Node'
32 | uses: actions/setup-node@v3
33 | with:
34 | node-version: 18
35 | - name: 'Install depependencies'
36 | run: |
37 | npm install
38 | - name: 'Build'
39 | run: |
40 | npm run build
41 | - name: 'Release'
42 | run: |
43 | npx semantic-release
44 | env:
45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | dist
4 | ~*
5 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock = false
2 |
--------------------------------------------------------------------------------
/.releaserc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | '@semantic-release/commit-analyzer',
4 | '@semantic-release/release-notes-generator',
5 | '@semantic-release/changelog',
6 | [
7 | '@semantic-release/npm',
8 | {
9 | pkgRoot: 'dist',
10 | },
11 | ],
12 | '@semantic-release/github',
13 | '@semantic-release/git',
14 | ],
15 | };
16 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # [7.2.0](https://github.com/unlight/postcss-import-url/compare/v7.1.0...v7.2.0) (2022-12-14)
2 |
3 |
4 | ### Features
5 |
6 | * Add support for base64 encoded data urls ([a70fc3c](https://github.com/unlight/postcss-import-url/commit/a70fc3c75ee5d2fc00cf5883c0387598b55fe438)), closes [#30](https://github.com/unlight/postcss-import-url/issues/30)
7 |
8 | # [7.1.0](https://github.com/unlight/postcss-import-url/compare/v7.0.0...v7.1.0) (2022-11-30)
9 |
10 |
11 | ### Features
12 |
13 | * Support imports into `[@layer](https://github.com/layer)` ([7f42a8e](https://github.com/unlight/postcss-import-url/commit/7f42a8eb0e0bd547a135413de0e157770615fcf5)), closes [#28](https://github.com/unlight/postcss-import-url/issues/28)
14 |
15 | # [7.0.0](https://github.com/unlight/postcss-import-url/compare/v6.0.4...v7.0.0) (2021-02-13)
16 |
17 |
18 | ### Bug Fixes
19 |
20 | * Set element source property [#27](https://github.com/unlight/postcss-import-url/issues/27) ([a4687df](https://github.com/unlight/postcss-import-url/commit/a4687dfb5c9a77531b6869711d1ce38d09d17a6a))
21 |
22 |
23 | ### BREAKING CHANGES
24 |
25 | * Node LTS is required (v12+)
26 |
27 | ## [6.0.4](https://github.com/unlight/postcss-import-url/compare/v6.0.3...v6.0.4) (2021-02-13)
28 |
29 |
30 | ### Bug Fixes
31 |
32 | * Revert set element source property ([da4afb8](https://github.com/unlight/postcss-import-url/commit/da4afb82207ebcb04871eac184e2866528f5f9a1))
33 |
34 | ## [6.0.3](https://github.com/unlight/postcss-import-url/compare/v6.0.2...v6.0.3) (2021-02-13)
35 |
36 |
37 | ### Bug Fixes
38 |
39 | * Set element source property ([da96061](https://github.com/unlight/postcss-import-url/commit/da96061bdd53253e02bc23e1ce86d2e38347aeed)), closes [#27](https://github.com/unlight/postcss-import-url/issues/27)
40 |
41 | ## [6.0.2](https://github.com/unlight/postcss-import-url/compare/v6.0.1...v6.0.2) (2021-02-13)
42 |
43 |
44 | ### Bug Fixes
45 |
46 | * Revert set element source property ([e7207c8](https://github.com/unlight/postcss-import-url/commit/e7207c8ea7ad2f28e8ae2771781b4e20d38dd8e3))
47 |
48 | ## [6.0.1](https://github.com/unlight/postcss-import-url/compare/v6.0.0...v6.0.1) (2021-02-13)
49 |
50 |
51 | ### Bug Fixes
52 |
53 | * Set element source property ([8405f32](https://github.com/unlight/postcss-import-url/commit/8405f32b9aa09bef534e67717bf7de3d52477547)), closes [#27](https://github.com/unlight/postcss-import-url/issues/27)
54 |
55 | # [6.0.0](https://github.com/unlight/postcss-import-url/compare/v5.1.0...v6.0.0) (2020-12-09)
56 |
57 |
58 | ### Features
59 |
60 | * Support PostCSS v8 ([2d17367](https://github.com/unlight/postcss-import-url/commit/2d173670da93ab88a428ade3a05a792f79503b7e))
61 |
62 |
63 | ### BREAKING CHANGES
64 |
65 | * Support PostCSS v8
66 |
67 | # [5.1.0](https://github.com/unlight/postcss-import-url/compare/v5.0.0...v5.1.0) (2020-04-04)
68 |
69 |
70 | ### Features
71 |
72 | * supports multiple url resolves ([4997931](https://github.com/unlight/postcss-import-url/commit/4997931bf216c8b740fae7518c13cb457e840053))
73 |
74 | # [5.0.0](https://github.com/unlight/postcss-import-url/compare/v4.0.0...v5.0.0) (2020-03-01)
75 |
76 |
77 | ### chore
78 |
79 | * Added development stuff (prettier, eslint, etc.) ([70e5497](https://github.com/unlight/postcss-import-url/commit/70e5497a750dd7e7935ed4f08fde76f30b69b955))
80 |
81 |
82 | ### Features
83 |
84 | * Resolve relative URLs in property values ([c11c03d](https://github.com/unlight/postcss-import-url/commit/c11c03d10f8d5016d4cec811f40fed6f6140e6f1))
85 |
86 |
87 | ### BREAKING CHANGES
88 |
89 | * Node 10+ is required
90 |
91 | # [4.0.0](https://github.com/unlight/postcss-import-url/compare/v3.0.4...v4.0.0) (2019-01-03)
92 |
93 |
94 | ### chore
95 |
96 | * Updated dependencies ([b1ed8b2](https://github.com/unlight/postcss-import-url/commit/b1ed8b2))
97 |
98 |
99 | ### Features
100 |
101 | * Added semantic release ([69c494a](https://github.com/unlight/postcss-import-url/commit/69c494a))
102 |
103 |
104 | ### BREAKING CHANGES
105 |
106 | * Node 6+ is required now
107 |
108 | ## 3.X.X
109 |
110 | * 3.0.4 (04 Jan 2018) - fixed [#15](https://github.com/unlight/postcss-import-url/issues/15)
111 | * 3.0.3 (02 Jan 2018) - fixed [#13](https://github.com/unlight/postcss-import-url/issues/13), set peer dependency as range >=6 <7
112 | * 3.0.1 (30 Oct 2017) - updated dependencies (postcss 6), moved postcss to peerDependencies
113 | * 3.0.0 (27 Oct 2017) - using `atRule.source.input.file` to resolve urls
114 | * 2.2.0 (19 Nov 2016) - added option modernBrowser
115 | * 2.1.1 (19 Oct 2016) - added to readme google font notice
116 | * 2.1.0 (09 Jul 2016) - replaced Object.assign by _.assign
117 | * 2.0.0 (08 Jul 2016) - added recursive option
118 | * 1.0.0 (01 Nov 2015) - first release
119 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # postcss-import-url
2 |
3 | [PostCSS](https://github.com/postcss/postcss) plugin inlines remote files.
4 |
5 | ```css
6 | /* Input example */
7 | @import 'https://fonts.googleapis.com/css?family=Tangerine';
8 | body {
9 | font-size: 13px;
10 | }
11 | ```
12 |
13 | ```css
14 | /* Output example */
15 | @font-face {
16 | font-family: 'Tangerine';
17 | font-style: normal;
18 | font-weight: 400;
19 | src: url(https://fonts.gstatic.com/s/tangerine/v12/IurY6Y5j_oScZZow4VOxCZZM.woff2)
20 | format('woff2');
21 | }
22 | body {
23 | font-size: 13px;
24 | }
25 | ```
26 |
27 | ## Usage
28 |
29 | ```js
30 | const importUrl = require('postcss-import-url');
31 | const options = {};
32 | postcss([importUrl(options)]).process(css, {
33 | // Define a `from` option to resolve relative @imports in the initial css to a url.
34 | from: 'https://example.com/styles.css',
35 | });
36 | ```
37 |
38 | See [PostCSS](https://github.com/postcss/postcss#usage) docs for examples for your environment.
39 |
40 | ## Options
41 |
42 | - `recursive` (boolean) To import URLs recursively (default: `true`)
43 | - `resolveUrls` (boolean) To transform relative URLs found in remote stylesheets into fully qualified URLs ([see #18](https://github.com/unlight/postcss-import-url/pull/18)) (default: `false`)
44 | - `modernBrowser` (boolean) Set user-agent string to 'Mozilla/5.0 AppleWebKit/537.36 Chrome/80.0.0.0 Safari/537.36', this option maybe useful for importing fonts from Google. Google check `user-agent` header string and respond can be different (default: `false`)
45 | - `userAgent` (string) Custom user-agent header (default: `null`)
46 | - `dataUrls` (boolean) Store fetched CSS as base64 encoded data URLs (default: `false`)
47 |
48 | ## Known Issues
49 |
50 | - Google fonts returns different file types per the user agent. Because postcss runs in a shell,
51 | Google returns truetype fonts rather than the better woff2 format.
52 | Use option `modernBrowser` to explicitly load woff2 fonts.
53 |
--------------------------------------------------------------------------------
/Taskfile:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | PATH="$PWD/node_modules/.bin":$PATH
3 | set -e
4 |
5 | build_cp() {
6 | set -x
7 | rm -rfv dist
8 | mkdir dist
9 | cp -rfv index.js dist
10 | cp -fv README.md LICENSE package.json dist
11 | set +x
12 | }
13 |
14 | "$@"
15 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var connect = require('gulp-connect');
3 | var mocha = require('gulp-mocha');
4 | var eslint = require('gulp-eslint');
5 |
6 | var files = ['index.js', 'gulpfile.js'];
7 |
8 | gulp.task('lint', function () {
9 | return gulp.src(files).pipe(eslint()).pipe(eslint.format());
10 | // .pipe(eslint.failAfterError());
11 | });
12 |
13 | gulp.task('test', function () {
14 | var startServer = gulp.task('start-server');
15 | startServer();
16 | return gulp
17 | .src('test/*.js', { read: false })
18 | .pipe(mocha({ timeout: 5000 }))
19 | .on('end', connect.serverClose);
20 | });
21 |
22 | gulp.task('test:w', function () {
23 | var startServer = gulp.task('start-server');
24 | startServer();
25 | return gulp
26 | .src('test/*.js', { read: false })
27 | .pipe(mocha({ timeout: 5000, watch: true }))
28 | .on('end', connect.serverClose);
29 | });
30 |
31 | gulp.task('default', gulp.series(['lint', 'test']));
32 |
33 | gulp.task('watch', function () {
34 | gulp.watch(files, gulp.series(['lint', 'test']));
35 | });
36 |
37 | gulp.task('start-server', function () {
38 | connect.server({
39 | root: './test',
40 | port: 1234,
41 | });
42 | });
43 |
44 | gulp.task('close-server', function () {
45 | connect.serverClose();
46 | });
47 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const postcss = require('postcss');
2 | const hh = require('http-https');
3 | const isUrl = require('is-url');
4 | const trim = require('lodash.trim');
5 | const resolveRelative = require('resolve-relative-url');
6 | const assign = require('lodash.assign');
7 | const url = require('url');
8 |
9 | const defaults = {
10 | recursive: true,
11 | resolveUrls: false,
12 | modernBrowser: false,
13 | userAgent: null,
14 | dataUrls: false,
15 | };
16 | const space = postcss.list.space;
17 | const urlRegexp = /url\(["']?.+?['"]?\)/g;
18 |
19 | function postcssImportUrl(options) {
20 | options = assign({}, defaults, options || {});
21 |
22 | async function importUrl(tree, _, parentRemoteFile) {
23 | parentRemoteFile = parentRemoteFile || tree.source.input.file;
24 | const imports = [];
25 | tree.walkAtRules('import', function checkAtRule(atRule) {
26 | const params = space(atRule.params);
27 | let remoteFile = cleanupRemoteFile(params[0]);
28 | if (parentRemoteFile) {
29 | remoteFile = resolveRelative(remoteFile, parentRemoteFile);
30 | }
31 | if (!isUrl(remoteFile)) {
32 | return;
33 | }
34 | imports[imports.length] = createPromise(remoteFile, options).then(
35 | async r => {
36 | let newNode = postcss.parse(r.body);
37 | let hasLayer = params.find(param => param.includes('layer'));
38 | let hasSupports = params.find(param => param.includes('supports'));
39 |
40 | const mediaQueries = params
41 | .slice(hasLayer ? (hasSupports ? 3 : 2) : 1)
42 | .join(' ');
43 |
44 | if (mediaQueries) {
45 | const mediaNode = postcss.atRule({
46 | name: 'media',
47 | params: mediaQueries,
48 | source: atRule.source,
49 | });
50 | mediaNode.append(newNode);
51 | newNode = mediaNode;
52 | } else {
53 | newNode.source = atRule.source;
54 | }
55 |
56 | if (hasSupports) {
57 | const supportQuery = params.find(param =>
58 | param.includes('supports'),
59 | );
60 |
61 | let init = supportQuery.indexOf('(');
62 | let fin = supportQuery.indexOf(')');
63 | let query = supportQuery.substr(init + 1, fin - init - 1);
64 |
65 | const supportsNode = postcss.atRule({
66 | name: 'supports',
67 | params: `(${query})`,
68 | source: atRule.source,
69 | });
70 | supportsNode.append(newNode);
71 | newNode = supportsNode;
72 | } else {
73 | newNode.source = atRule.source;
74 | }
75 |
76 | if (hasLayer) {
77 | const layer = params.find(param => param.includes('layer'));
78 |
79 | let init = layer.indexOf('(');
80 | let fin = layer.indexOf(')');
81 | let layerName = layer.substr(init + 1, fin - init - 1);
82 |
83 | const layerNode = postcss.atRule({
84 | name: 'layer',
85 | params: layerName,
86 | source: newNode.source,
87 | });
88 |
89 | layerNode.append(newNode);
90 | newNode = layerNode;
91 | }
92 |
93 | if (options.resolveUrls) {
94 | // Convert relative paths to absolute paths
95 | newNode = newNode.replaceValues(urlRegexp, { fast: 'url(' }, url =>
96 | resolveUrls(url, remoteFile),
97 | );
98 | }
99 |
100 | const importedTree = await (options.recursive
101 | ? importUrl(newNode, null, r.parent)
102 | : Promise.resolve(newNode));
103 |
104 | if (options.dataUrls) {
105 | atRule.params = `url(data:text/css;base64,${Buffer.from(importedTree.toString()).toString('base64')})`;
106 | } else {
107 | atRule.replaceWith(importedTree);
108 | }
109 | },
110 | );
111 | });
112 | await Promise.all(imports);
113 | return tree;
114 | }
115 |
116 | return {
117 | postcssPlugin: 'postcss-import-url',
118 | Once: importUrl,
119 | };
120 | }
121 |
122 | module.exports = postcssImportUrl;
123 | module.exports.postcss = true;
124 |
125 | function cleanupRemoteFile(value) {
126 | if (value.substr(0, 3) === 'url') {
127 | value = value.substr(3);
128 | }
129 | value = trim(value, '\'"()');
130 | return value;
131 | }
132 |
133 | function resolveUrls(to, from) {
134 | return 'url("' + resolveRelative(cleanupRemoteFile(to), from) + '")';
135 | }
136 |
137 | function createPromise(remoteFile, options) {
138 | const reqOptions = urlParse(remoteFile);
139 | reqOptions.headers = {};
140 | reqOptions.headers['connection'] = 'keep-alive';
141 | if (options.modernBrowser) {
142 | reqOptions.headers['user-agent'] =
143 | 'Mozilla/5.0 AppleWebKit/538.0 Chrome/88.0.0.0 Safari/538';
144 | }
145 | if (options.userAgent) {
146 | reqOptions.headers['user-agent'] = String(options.userAgent);
147 | }
148 | function executor(resolve, reject) {
149 | const request = hh.get(reqOptions, response => {
150 | let body = '';
151 | response.on('data', chunk => {
152 | body += chunk.toString();
153 | });
154 | response.on('end', () => {
155 | resolve({
156 | body: body,
157 | parent: remoteFile,
158 | });
159 | });
160 | });
161 | request.on('error', reject);
162 | request.end();
163 | }
164 | return new Promise(executor);
165 | }
166 |
167 | function urlParse(remoteFile) {
168 | const reqOptions = url.parse(remoteFile);
169 | return reqOptions;
170 | }
171 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "postcss-import-url",
3 | "version": "0.0.0-dev",
4 | "description": "PostCSS plugin inlines remote files.",
5 | "main": "index.js",
6 | "keywords": [
7 | "postcss",
8 | "css",
9 | "postcss-plugin"
10 | ],
11 | "license": "MIT",
12 | "homepage": "https://github.com/unlight/postcss-import-url",
13 | "engines": {
14 | "node": ">=10"
15 | },
16 | "scripts": {
17 | "build": "sh Taskfile build_cp",
18 | "eslint": "node node_modules/eslint/bin/eslint index.js",
19 | "eslint:fix": "npm run eslint -- --fix",
20 | "test": "gulp",
21 | "test:w": "gulp test:w"
22 | },
23 | "dependencies": {
24 | "http-https": "^1.0.0",
25 | "is-url": "^1.2.4",
26 | "lodash.assign": "^4.2.0",
27 | "lodash.trim": "^4.5.1",
28 | "resolve-relative-url": "^1.0.0"
29 | },
30 | "peerDependencies": {
31 | "postcss": "^8.0.0"
32 | },
33 | "devDependencies": {
34 | "@semantic-release/changelog": "^6.0.2",
35 | "@semantic-release/git": "^10.0.1",
36 | "expect": "^29.3.1",
37 | "gulp": "^4.0.2",
38 | "gulp-connect": "^5.7.0",
39 | "gulp-eslint": "^6.0.0",
40 | "gulp-mocha": "^8.0.0",
41 | "ololog": "^1.1.175",
42 | "postcss": "^8.2.4",
43 | "precise-commits": "^1.0.2",
44 | "prettier": "^2.8.0",
45 | "semantic-release": "^19.0.5",
46 | "tcp-ping": "^0.1.1"
47 | },
48 | "directories": {
49 | "test": "test"
50 | },
51 | "repository": {
52 | "type": "git",
53 | "url": "git+https://github.com/unlight/postcss-import-url.git"
54 | },
55 | "bugs": {
56 | "url": "https://github.com/unlight/postcss-import-url/issues"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/prettier.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 80,
3 | trailingComma: 'all',
4 | tabWidth: 2,
5 | semi: true,
6 | singleQuote: true,
7 | arrowParens: 'avoid',
8 | };
9 |
--------------------------------------------------------------------------------
/test/fixture-1/a.css:
--------------------------------------------------------------------------------
1 | @import 'a1.css';
2 | .a {
3 | content: '.a';
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixture-1/a1.css:
--------------------------------------------------------------------------------
1 | .a1 {
2 | content: '.a1';
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixture-1/main.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/fixture-1/style.css:
--------------------------------------------------------------------------------
1 | @import url('./a.css');
2 | .style {
3 | content: '.style';
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixture-2/a.css:
--------------------------------------------------------------------------------
1 | @import 'a1.css';
2 | a. {
3 | content: '.a';
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixture-2/a1.css:
--------------------------------------------------------------------------------
1 | .a1 {
2 | content: '.a1';
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixture-2/b.css:
--------------------------------------------------------------------------------
1 | @import './b1.css';
2 | .b {
3 | content: '.b';
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixture-2/b1.css:
--------------------------------------------------------------------------------
1 | .b1 {
2 | content: '.b1';
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixture-2/main.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/fixture-2/style.css:
--------------------------------------------------------------------------------
1 | @import url('./a.css');
2 | @import url(http://localhost:1234/fixture-2/b.css);
3 | .style {
4 | content: '.style';
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixture-3/a.css:
--------------------------------------------------------------------------------
1 | @import './recursive/b.css';
2 |
3 | @font-face {
4 | src: url('./font.woff');
5 | }
6 | .implicit-sibling {
7 | background-image: url('implicit-sibling.png');
8 | }
9 | .absolute {
10 | background-image: url('http://example.com/absolute.png');
11 | }
12 | .root-relative {
13 | background-image: url('/root-relative.png');
14 | }
15 | .sibling {
16 | background-image: url('./sibling.png');
17 | }
18 | .parent {
19 | background-image: url('../parent.png');
20 | }
21 | .grandparent {
22 | background-image: url('../../grandparent.png');
23 | }
24 |
--------------------------------------------------------------------------------
/test/fixture-3/main.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/fixture-3/recursive/b.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | src: url('../font-recursive.woff');
3 | }
4 |
5 | .sibling-recursive {
6 | background-image: url('./sibling-recursive.png');
7 | }
8 |
9 | .parent-recursive {
10 | background-image: url('../parent-recursive.png');
11 | }
12 |
13 | .grandparent-recursive {
14 | background-image: url('../../grandparent-recursive.png');
15 | }
16 |
17 | .absolute-recursive {
18 | background-image: url('http://example.com/absolute-recursive.png');
19 | }
20 |
--------------------------------------------------------------------------------
/test/fixture-3/style.css:
--------------------------------------------------------------------------------
1 | @import url(http://localhost:1234/fixture-3/a.css);
2 | .style {
3 | content: '.style';
4 | }
5 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | const postcss = require('postcss');
2 | const { default: expect } = require('expect');
3 | const fs = require('fs');
4 | const plugin = require('../');
5 | const tcpp = require('tcp-ping');
6 | const log = require('ololog');
7 |
8 | const fixture1Css = fs.readFileSync(__dirname + '/fixture-1/style.css', {
9 | encoding: 'utf8',
10 | });
11 |
12 | const testEqual = function (
13 | input,
14 | output,
15 | pluginOptions,
16 | postcssOptions,
17 | done,
18 | ) {
19 | getResult(input, pluginOptions, postcssOptions).then(result => {
20 | expect(result.css.trim()).toEqual(output.trim());
21 | expect(result.warnings()).toHaveLength(0);
22 | done();
23 | }, done);
24 | };
25 |
26 | const testContains = function (
27 | input,
28 | value,
29 | pluginOptions,
30 | postcssOptions,
31 | done,
32 | ) {
33 | getResult(input, pluginOptions, postcssOptions).then(result => {
34 | expect(result.css).toContain(value);
35 | expect(result.warnings()).toHaveLength(0);
36 | done();
37 | }, done);
38 | };
39 |
40 | async function getResult(input, pluginOptions, postcssOptions) {
41 | return postcss([plugin(pluginOptions)]).process(input, {
42 | from: undefined,
43 | ...postcssOptions,
44 | });
45 | }
46 |
47 | describe('import with media queries', function () {
48 | it('only screen', async () => {
49 | const input =
50 | "@import 'http://fonts.googleapis.com/css?family=Tangerine' only screen and (color)";
51 | const result = await getResult(input);
52 | expect(result.css).toContain('@media only screen and (color)');
53 | });
54 |
55 | it('rule with and', function (done) {
56 | const input =
57 | "@import 'http://fonts.googleapis.com/css?family=Tangerine' screen and (orientation:landscape)";
58 | testContains(
59 | input,
60 | '@media screen and (orientation:landscape)',
61 | {},
62 | {},
63 | done,
64 | );
65 | });
66 |
67 | it('rule projection, tv', function (done) {
68 | const input =
69 | "@import url('http://fonts.googleapis.com/css?family=Tangerine') projection, tv";
70 | testContains(input, '@media projection, tv', {}, {}, done);
71 | });
72 |
73 | it('rule print', function (done) {
74 | const input =
75 | "@import url('http://fonts.googleapis.com/css?family=Tangerine') print";
76 | testContains(input, '@media print', {}, {}, done);
77 | });
78 |
79 | it('rule layer', function (done) {
80 | const input =
81 | "@import url('http://fonts.googleapis.com/css?family=Tangerine') layer(test);";
82 | testContains(input, '@layer test {', {}, {}, done);
83 | });
84 |
85 | it('rule anonymous layer', function (done) {
86 | const input =
87 | "@import url('http://fonts.googleapis.com/css?family=Tangerine') layer;";
88 | testContains(input, '@layer {', {}, {}, done);
89 | });
90 |
91 | it('contains it', function (done) {
92 | const input =
93 | "@import url('http://fonts.googleapis.com/css?family=Tangerine') (min-width: 25em);";
94 | testContains(input, '(min-width: 25em)', {}, {}, done);
95 | });
96 |
97 | describe('media query', function () {
98 | it('contains font-family', function (done) {
99 | const input =
100 | "@import url('http://fonts.googleapis.com/css?family=Tangerine') (min-width: 25em);";
101 | testContains(input, "font-family: 'Tangerine'", {}, {}, done);
102 | });
103 |
104 | it('contains src local', async () => {
105 | const input =
106 | "@import url('http://fonts.googleapis.com/css?family=Tangerine') (min-width: 25em);";
107 | const result = await getResult(input);
108 | expect(result.css).toContain('@media (min-width: 25em) {@font-face');
109 | });
110 |
111 | it('contains layer', function (done) {
112 | const input =
113 | "@import url('http://fonts.googleapis.com/css?family=Tangerine') layer(test) (min-width: 25em);";
114 | testContains(
115 | input,
116 | '@layer test {@media (min-width: 25em) {@font-face',
117 | {},
118 | {},
119 | done,
120 | );
121 | });
122 |
123 | it('contains layer with @supports', function (done) {
124 | const input =
125 | "@import url('http://fonts.googleapis.com/css?family=Tangerine') layer(test) @supports(display: flex);";
126 | testContains(
127 | input,
128 | '@layer test {@supports (display: flex) {@font-face',
129 | {},
130 | {},
131 | done,
132 | );
133 | });
134 |
135 | it('contains layer with @supports and @media', function (done) {
136 | const input =
137 | "@import url('http://fonts.googleapis.com/css?family=Tangerine') layer(test) @supports(display: flex) (min-width: 25em);";
138 | testContains(
139 | input,
140 | '@layer test {@supports (display: flex) {@media (min-width: 25em) {@font-face',
141 | {},
142 | {},
143 | done,
144 | );
145 | });
146 | });
147 | });
148 |
149 | describe('skip non remote files', function () {
150 | it('local', function (done) {
151 | testEqual("@import 'a.css';", "@import 'a.css';", {}, {}, done);
152 | });
153 |
154 | it('relative parent', function (done) {
155 | const input = "@import '../a.css'";
156 | testEqual(input, input, {}, {}, done);
157 | });
158 |
159 | it('relative child', function (done) {
160 | const input = "@import './a/b.css'";
161 | testEqual(input, input, {}, {}, done);
162 | });
163 |
164 | it.skip('no protocol', async () => {
165 | const input = '@import url(//example.com/a.css)';
166 | const result = await getResult(input);
167 | });
168 | });
169 |
170 | describe('import url tangerine', function () {
171 | function assertOutputTangerine(result) {
172 | expect(result.css).toContain("font-family: 'Tangerine'");
173 | expect(result.css).toContain('font-style: normal');
174 | expect(result.css).toContain('font-weight: 400');
175 | expect(result.css).toContain('fonts.gstatic.com/s/tangerine');
176 | }
177 |
178 | it('empty', async () => {
179 | const input =
180 | "@import 'http://fonts.googleapis.com/css?family=Tangerine' ;";
181 | const result = await getResult(input);
182 | assertOutputTangerine(result);
183 | });
184 |
185 | it('double quotes', async () => {
186 | const input = '@import "http://fonts.googleapis.com/css?family=Tangerine";';
187 | const result = await getResult(input);
188 | assertOutputTangerine(result);
189 | });
190 |
191 | it('single quotes', async () => {
192 | const input = "@import 'http://fonts.googleapis.com/css?family=Tangerine';";
193 | const result = await getResult(input);
194 | assertOutputTangerine(result);
195 | });
196 |
197 | it('url single quotes', async () => {
198 | const input =
199 | "@import url('http://fonts.googleapis.com/css?family=Tangerine');";
200 | const result = await getResult(input);
201 | assertOutputTangerine(result);
202 | });
203 |
204 | it('url double quotes', async () => {
205 | const input =
206 | '@import url("http://fonts.googleapis.com/css?family=Tangerine");';
207 | const result = await getResult(input);
208 | assertOutputTangerine(result);
209 | });
210 |
211 | it('url no quotes', async () => {
212 | const input =
213 | '@import url(http://fonts.googleapis.com/css?family=Tangerine);';
214 | const result = await getResult(input);
215 | assertOutputTangerine(result);
216 | });
217 | });
218 |
219 | describe('recursive import', function () {
220 | it('ping server', done => {
221 | tcpp.probe('localhost', 1234, function (err) {
222 | done(err);
223 | });
224 | });
225 |
226 | var opts = {
227 | recursive: true,
228 | };
229 |
230 | describe('fixture-1', function () {
231 | it('fixture-1 contains class a1', function (done) {
232 | const input = '@import url(http://localhost:1234/fixture-1/style.css)';
233 | testContains(input, '.a1', opts, {}, done);
234 | });
235 |
236 | it('fixture-1 contains class a', function (done) {
237 | const input = '@import url(http://localhost:1234/fixture-1/style.css)';
238 | testContains(input, "content: '.a'", opts, {}, done);
239 | });
240 |
241 | it('fixture-1 contains class style content', function (done) {
242 | const input = '@import url(http://localhost:1234/fixture-1/style.css)';
243 | testContains(input, "content: '.style'", opts, {}, done);
244 | });
245 |
246 | it('fixture-1 contains class a when passed as a string', function (done) {
247 | const input = fixture1Css;
248 | testContains(
249 | input,
250 | "content: '.a'",
251 | opts,
252 | {
253 | from: 'http://localhost:1234/fixture-1/style.css',
254 | },
255 | done,
256 | );
257 | });
258 | });
259 |
260 | describe('fixture-2', function () {
261 | it('fixture-2 contains class a1', function (done) {
262 | const input = '@import url(http://localhost:1234/fixture-2/style.css)';
263 | testContains(input, "content: '.a1'", opts, {}, done);
264 | });
265 |
266 | it('fixture-2 contains class a', function (done) {
267 | const input = '@import url(http://localhost:1234/fixture-2/style.css)';
268 | testContains(input, "content: '.a'", opts, {}, done);
269 | });
270 |
271 | it('fixture-2 contains class b1', function (done) {
272 | const input = '@import url(http://localhost:1234/fixture-2/style.css)';
273 | testContains(input, "content: '.b1'", opts, {}, done);
274 | });
275 |
276 | it('fixture-2 contains class b', function (done) {
277 | const input = '@import url(http://localhost:1234/fixture-2/style.css)';
278 | testContains(input, "content: '.b'", opts, {}, done);
279 | });
280 |
281 | it('fixture-2 contains class style content', function (done) {
282 | const input = '@import url(http://localhost:1234/fixture-2/style.css)';
283 | testContains(input, "content: '.style'", opts, {}, done);
284 | });
285 | });
286 |
287 | describe('fixture-3 convert relative paths in property values', function () {
288 | it('does not resolve relative URLs by default', function (done) {
289 | const input = '@import url(http://localhost:1234/fixture-3/style.css)';
290 | testContains(input, "src: url('./font.woff');", {}, {}, done);
291 | });
292 |
293 | it('does not resolve relative URLs when option.resolveURLs is false', function (done) {
294 | const input = '@import url(http://localhost:1234/fixture-3/style.css)';
295 | testContains(
296 | input,
297 | "src: url('./font.woff');",
298 | { resolveUrls: false },
299 | {},
300 | done,
301 | );
302 | });
303 |
304 | var _opts = { resolveUrls: true };
305 |
306 | it('resolves relative URLs when option.resolveURLs is true', function (done) {
307 | const input = '@import url(http://localhost:1234/fixture-3/style.css)';
308 | testContains(
309 | input,
310 | 'src: url("http://localhost:1234/fixture-3/font.woff");',
311 | _opts,
312 | {},
313 | done,
314 | );
315 | });
316 |
317 | it('does not modify absolute paths', function (done) {
318 | const input = '@import url(http://localhost:1234/fixture-3/style.css)';
319 | testContains(
320 | input,
321 | 'background-image: url("http://example.com/absolute.png");',
322 | _opts,
323 | {},
324 | done,
325 | );
326 | });
327 |
328 | it('makes root relative paths absolute', function (done) {
329 | const input = '@import url(http://localhost:1234/fixture-3/style.css)';
330 | testContains(
331 | input,
332 | 'background-image: url("http://localhost:1234/root-relative.png")',
333 | _opts,
334 | {},
335 | done,
336 | );
337 | });
338 |
339 | it('makes implicit sibling paths absolute', function (done) {
340 | const input = '@import url(http://localhost:1234/fixture-3/style.css)';
341 | testContains(
342 | input,
343 | 'background-image: url("http://localhost:1234/fixture-3/implicit-sibling.png")',
344 | _opts,
345 | {},
346 | done,
347 | );
348 | });
349 |
350 | it('makes relative sibling paths absolute', function (done) {
351 | const input = '@import url(http://localhost:1234/fixture-3/style.css)';
352 | testContains(
353 | input,
354 | 'background-image: url("http://localhost:1234/fixture-3/sibling.png")',
355 | _opts,
356 | {},
357 | done,
358 | );
359 | });
360 |
361 | it('makes parent relative paths absolute', function (done) {
362 | const input = '@import url(http://localhost:1234/fixture-3/style.css)';
363 | testContains(
364 | input,
365 | 'background-image: url("http://localhost:1234/parent.png")',
366 | _opts,
367 | {},
368 | done,
369 | );
370 | });
371 |
372 | it('makes grandparent relative paths absolute', function (done) {
373 | const input = '@import url(http://localhost:1234/fixture-3/style.css)';
374 | testContains(
375 | input,
376 | 'background-image: url("http://localhost:1234/grandparent.png")',
377 | _opts,
378 | {},
379 | done,
380 | );
381 | });
382 |
383 | var _optsRecursive = { resolveUrls: true, recursive: true };
384 |
385 | // Test paths are resolved for recursively imported stylesheets
386 | it('makes relative sibling paths absolute - recursive', function (done) {
387 | const input = '@import url(http://localhost:1234/fixture-3/style.css)';
388 | testContains(
389 | input,
390 | 'background-image: url("http://localhost:1234/fixture-3/recursive/sibling-recursive.png")',
391 | _optsRecursive,
392 | {},
393 | done,
394 | );
395 | });
396 |
397 | it('makes parent relative paths absolute - recursive', function (done) {
398 | const input = '@import url(http://localhost:1234/fixture-3/style.css)';
399 | testContains(
400 | input,
401 | 'background-image: url("http://localhost:1234/fixture-3/parent-recursive.png")',
402 | _optsRecursive,
403 | {},
404 | done,
405 | );
406 | });
407 |
408 | it('makes grandparent relative paths absolute - recursive', function (done) {
409 | const input = '@import url(http://localhost:1234/fixture-3/style.css)';
410 | testContains(
411 | input,
412 | 'background-image: url("http://localhost:1234/grandparent-recursive.png")',
413 | _optsRecursive,
414 | {},
415 | done,
416 | );
417 | });
418 | });
419 | });
420 |
421 | describe('google font woff', function () {
422 | it('option modernBrowser should import woff', function (done) {
423 | const input =
424 | '@import url(http://fonts.googleapis.com/css?family=Tangerine);';
425 | testContains(
426 | input,
427 | "woff2) format('woff2')",
428 | { modernBrowser: true },
429 | {},
430 | done,
431 | );
432 | });
433 |
434 | it('option agent should import woff', function (done) {
435 | const input =
436 | '@import url(http://fonts.googleapis.com/css?family=Tangerine);';
437 | var opts = {
438 | userAgent:
439 | 'Mozilla/5.0 AppleWebKit/537.36 Chrome/80.0.2840.99 Safari/537.36',
440 | };
441 | testContains(input, "woff2) format('woff2')", opts, {}, done);
442 | });
443 | });
444 |
445 | describe('source property', () => {
446 | it('regular import', async () => {
447 | const input =
448 | '@import url(http://fonts.googleapis.com/css?family=Tangerine)';
449 | const result = await getResult(input);
450 | expect(result.root.source.input.css).toEqual(input);
451 | });
452 |
453 | it('media import', async () => {
454 | const input =
455 | '@import url(http://fonts.googleapis.com/css?family=Tangerine) print';
456 | const result = await getResult(input);
457 | expect(result.root.source.input.css).toEqual(input);
458 | });
459 | });
460 |
461 | describe('base64 data urls', function () {
462 | it('option dataUrls should converts imports to base64 encoded data urls', async () => {
463 | const input = '@import url(http://fonts.googleapis.com/css?family=Tangerine);';
464 | const result = await getResult(input, { dataUrls: true });
465 | expect(result.css.trim()).toEqual('@import url(data:text/css;base64,QGZvbnQtZmFjZSB7CiAgZm9udC1mYW1pbHk6ICdUYW5nZXJpbmUnOwogIGZvbnQtc3R5bGU6IG5vcm1hbDsKICBmb250LXdlaWdodDogNDAwOwogIHNyYzogdXJsKGh0dHA6Ly9mb250cy5nc3RhdGljLmNvbS9zL3RhbmdlcmluZS92MTcvSXVyWTZZNWpfb1NjWlpvdzRWT3hDWlpKLnR0ZikgZm9ybWF0KCd0cnVldHlwZScpOwp9Cg==);');
466 | });
467 | });
468 |
--------------------------------------------------------------------------------