diff --git a/package.json b/package.json
index 0deb31b2..2cae5d30 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,7 @@
"source-map": "^0.6.1",
"style-loader": "^2.0.0",
"stylus": "^0.54.7",
- "stylus-loader": "^3.0.2",
+ "stylus-loader": "^4.1.1",
"sugarss": "^3.0.1",
"ts-jest": "^26.2.0",
"ts-loader": "^8.0.6",
diff --git a/src/index.ts b/src/index.ts
index 692da9ec..cafbef86 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -173,32 +173,35 @@ export default function loader(
// styles
let stylesCode = ``
let hasCSSModules = false
+ const nonWhitespaceRE = /\S+/
if (descriptor.styles.length) {
- descriptor.styles.forEach((style: SFCStyleBlock, i: number) => {
- const src = style.src || resourcePath
- const attrsQuery = attrsToQuery(style.attrs, 'css')
- // make sure to only pass id when necessary so that we don't inject
- // duplicate tags when multiple components import the same css file
- const idQuery = style.scoped ? `&id=${id}` : ``
- const query = `?vue&type=style&index=${i}${idQuery}${attrsQuery}${resourceQuery}`
- const styleRequest = stringifyRequest(src + query)
- if (style.module) {
- if (!hasCSSModules) {
- stylesCode += `\nconst cssModules = script.__cssModules = {}`
- hasCSSModules = true
+ descriptor.styles
+ .filter((style) => style.src || nonWhitespaceRE.test(style.content))
+ .forEach((style: SFCStyleBlock, i: number) => {
+ const src = style.src || resourcePath
+ const attrsQuery = attrsToQuery(style.attrs, 'css')
+ // make sure to only pass id when necessary so that we don't inject
+ // duplicate tags when multiple components import the same css file
+ const idQuery = style.scoped ? `&id=${id}` : ``
+ const query = `?vue&type=style&index=${i}${idQuery}${attrsQuery}${resourceQuery}`
+ const styleRequest = stringifyRequest(src + query)
+ if (style.module) {
+ if (!hasCSSModules) {
+ stylesCode += `\nconst cssModules = script.__cssModules = {}`
+ hasCSSModules = true
+ }
+ stylesCode += genCSSModulesCode(
+ id,
+ i,
+ styleRequest,
+ style.module,
+ needsHotReload
+ )
+ } else {
+ stylesCode += `\nimport ${styleRequest}`
}
- stylesCode += genCSSModulesCode(
- id,
- i,
- styleRequest,
- style.module,
- needsHotReload
- )
- } else {
- stylesCode += `\nimport ${styleRequest}`
- }
- // TODO SSR critical CSS collection
- })
+ // TODO SSR critical CSS collection
+ })
}
let code = [
diff --git a/test/advanced.spec.ts b/test/advanced.spec.ts
index 40c3fa7a..e87482fa 100644
--- a/test/advanced.spec.ts
+++ b/test/advanced.spec.ts
@@ -1,6 +1,8 @@
import { SourceMapConsumer } from 'source-map'
import { fs as mfs } from 'memfs'
-import { bundle, mockBundleAndRun } from './utils'
+import { bundle, mockBundleAndRun, normalizeNewline, genId } from './utils'
+
+const MiniCssExtractPlugin = require('mini-css-extract-plugin')
test('support chaining with other loaders', async () => {
const { componentModule } = await mockBundleAndRun({
@@ -83,9 +85,67 @@ test.skip('source map', async () => {
expect(pos.line).toBe(9)
})
-test.todo('extract CSS')
+test('extract CSS', async () => {
+ await bundle({
+ entry: 'extract-css.vue',
+ modify: (config: any) => {
+ config.module.rules = [
+ {
+ test: /\.vue$/,
+ use: 'vue-loader',
+ },
+ {
+ test: /\.css$/,
+ use: [MiniCssExtractPlugin.loader, 'css-loader'],
+ },
+ {
+ test: /\.stylus$/,
+ use: [MiniCssExtractPlugin.loader, 'css-loader', 'stylus-loader'],
+ },
+ ]
+ },
+ plugins: [
+ new MiniCssExtractPlugin({
+ filename: 'test.output.css',
+ }),
+ ],
+ })
+
+ const css = normalizeNewline(mfs.readFileSync('/test.output.css').toString())
+ const id = `data-v-${genId('extract-css.vue')}`
+ expect(css).toContain(`h1 {\n color: #f00;\n}`)
+ // extract + scoped
+ expect(css).toContain(`h2[${id}] {\n color: green;\n}`)
+})
-test.todo('extract CSS with code spliting')
+// #1464
+test('extract CSS with code spliting', async () => {
+ await bundle({
+ entry: 'extract-css-chunks.vue',
+ modify: (config: any) => {
+ config.module.rules = [
+ {
+ test: /\.vue$/,
+ use: 'vue-loader',
+ },
+ {
+ test: /\.css$/,
+ use: [MiniCssExtractPlugin.loader, 'css-loader'],
+ },
+ ]
+ },
+ plugins: [
+ new MiniCssExtractPlugin({
+ filename: 'test.output.css',
+ }),
+ ],
+ })
+
+ const css = normalizeNewline(mfs.readFileSync('/test.output.css').toString())
+ expect(css).toContain(`h1 {\n color: red;\n}`)
+ expect(mfs.existsSync('/empty.test.output.css')).toBe(false)
+ expect(mfs.existsSync('/basic.test.output.css')).toBe(true)
+})
test.todo('support rules with oneOf')
diff --git a/test/fixtures/empty-style.vue b/test/fixtures/empty-style.vue
new file mode 100644
index 00000000..12b50f3a
--- /dev/null
+++ b/test/fixtures/empty-style.vue
@@ -0,0 +1,6 @@
+
+ empty style
+
+
+
diff --git a/test/fixtures/extract-css-chunks.vue b/test/fixtures/extract-css-chunks.vue
new file mode 100644
index 00000000..80c5e182
--- /dev/null
+++ b/test/fixtures/extract-css-chunks.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/extract-css.vue b/test/fixtures/extract-css.vue
new file mode 100644
index 00000000..7fed1096
--- /dev/null
+++ b/test/fixtures/extract-css.vue
@@ -0,0 +1,10 @@
+
+
+
diff --git a/yarn.lock b/yarn.lock
index bb4b9caa..b4f4e7a5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1052,6 +1052,27 @@
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
+"@nodelib/fs.scandir@2.1.3":
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
+ integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==
+ dependencies:
+ "@nodelib/fs.stat" "2.0.3"
+ run-parallel "^1.1.9"
+
+"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3"
+ integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==
+
+"@nodelib/fs.walk@^1.2.3":
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976"
+ integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==
+ dependencies:
+ "@nodelib/fs.scandir" "2.1.3"
+ fastq "^1.6.0"
+
"@sinonjs/commons@^1.7.0":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
@@ -3489,6 +3510,18 @@ fast-deep-equal@^3.1.1:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+fast-glob@^3.2.4:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3"
+ integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.0"
+ merge2 "^1.3.0"
+ micromatch "^4.0.2"
+ picomatch "^2.2.1"
+
fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@@ -3499,6 +3532,13 @@ fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
+fastq@^1.6.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.9.0.tgz#e16a72f338eaca48e91b5c23593bcc2ef66b7947"
+ integrity sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==
+ dependencies:
+ reusify "^1.0.4"
+
faye-websocket@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
@@ -3779,7 +3819,7 @@ glob-parent@^3.1.0:
is-glob "^3.1.0"
path-dirname "^1.0.0"
-glob-parent@~5.1.0:
+glob-parent@^5.1.0, glob-parent@~5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
@@ -5229,11 +5269,6 @@ lodash.camelcase@^4.3.0:
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
-lodash.clonedeep@^4.5.0:
- version "4.5.0"
- resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
- integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
-
lodash.memoize@4.x:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -5411,6 +5446,11 @@ merge-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+merge2@^1.3.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+ integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@@ -6820,6 +6860,11 @@ retry@^0.12.0:
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
+reusify@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
+ integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+
right-align@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
@@ -6854,6 +6899,11 @@ rsvp@^4.8.4:
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
+run-parallel@^1.1.9:
+ version "1.1.10"
+ resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef"
+ integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==
+
run-queue@^1.0.0, run-queue@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
@@ -7513,14 +7563,16 @@ style-loader@^2.0.0:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
-stylus-loader@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/stylus-loader/-/stylus-loader-3.0.2.tgz#27a706420b05a38e038e7cacb153578d450513c6"
- integrity sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==
+stylus-loader@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/stylus-loader/-/stylus-loader-4.1.1.tgz#0e94f5d6274932a2dad054d1a736b32146ac7a99"
+ integrity sha512-Vnm7J/nIs/P6swIrdwJW/dflhsCOiFmb1U3PeQ6phRtg1soPLN4uKnnL7AtGIJDe173elbtYIXVzmCyF493CfA==
dependencies:
- loader-utils "^1.0.2"
- lodash.clonedeep "^4.5.0"
- when "~3.6.x"
+ fast-glob "^3.2.4"
+ klona "^2.0.4"
+ loader-utils "^2.0.0"
+ normalize-path "^3.0.0"
+ schema-utils "^3.0.0"
stylus@^0.54.7:
version "0.54.8"
@@ -8275,11 +8327,6 @@ whatwg-url@^8.0.0:
tr46 "^2.0.2"
webidl-conversions "^6.1.0"
-when@~3.6.x:
- version "3.6.4"
- resolved "https://registry.yarnpkg.com/when/-/when-3.6.4.tgz#473b517ec159e2b85005497a13983f095412e34e"
- integrity sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=
-
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"