Skip to content

Commit

Permalink
feat: support transpilation of <style> blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
danielroe committed Feb 4, 2022
1 parent 09973f7 commit 8f3a951
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 27 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"globby": "^11.0.3",
"jiti": "^1.12.9",
"mri": "^1.2.0",
"pathe": "^0.2.0"
"pathe": "^0.2.0",
"sass": "^1.49.7"
},
"devDependencies": {
"@nuxtjs/eslint-config-typescript": "latest",
Expand Down
10 changes: 5 additions & 5 deletions src/loader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { vueLoader, jsLoader } from './loaders'
import { vueLoader, jsLoader, sassLoader } from './loaders'

export interface InputFile {
path: string
Expand All @@ -25,20 +25,20 @@ export type LoaderResult = OutputFile[] | undefined
export type LoadFile = (input: InputFile) => LoaderResult | Promise<LoaderResult>

export interface LoaderOptions {
ext?: 'mjs' | 'js' | 'ts',
format?: 'cjs' | 'esm',
ext?: 'mjs' | 'js' | 'ts'
format?: 'cjs' | 'esm'
declaration?: boolean
}

export interface LoaderContext {
loadFile: LoadFile,
loadFile: LoadFile
options: LoaderOptions
}

export type Loader = (input: InputFile, context: LoaderContext)
=> LoaderResult | Promise<LoaderResult>

export const defaultLoaders: Loader[] = [vueLoader, jsLoader]
export const defaultLoaders: Loader[] = [vueLoader, jsLoader, sassLoader]

export interface CreateLoaderOptions extends LoaderOptions {
loaders?: Loader[]
Expand Down
1 change: 1 addition & 0 deletions src/loaders/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './js'
export * from './sass'
export * from './vue'
21 changes: 21 additions & 0 deletions src/loaders/sass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { compileString } from 'sass'

import type { Loader, LoaderResult } from '../loader'

export const sassLoader: Loader = async (input, { options }) => {
if (!['.sass', '.scss'].includes(input.extension)) {
return
}

const output: LoaderResult = []

const contents = await input.getContents()

output.push({
contents: compileString(contents).css,
path: input.path,
extension: `.${options.ext || 'css'}`
})

return output
}
72 changes: 57 additions & 15 deletions src/loaders/vue.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,86 @@
import type { Loader } from '../loader'
import type { Loader, LoaderResult } from '../loader'

export const vueLoader: Loader = async (input, { loadFile }) => {
export const vueLoader: Loader = async (input, ctx) => {
if (input.extension !== '.vue') {
return
}

const output: LoaderResult = [{
path: input.path,
contents: await input.getContents()
}]

let earlyReturn = true

for (const blockLoader of [sassLoader, scriptLoader]) {
const result = await blockLoader({ ...input, getContents: () => output[0].contents! }, ctx)
if (!result) { continue }

earlyReturn = false
const [vueFile, ...files] = result
output[0] = vueFile
output.push(...files)
}

if (earlyReturn) { return }

return output
}

interface BlockLoaderOptions {
type: 'script' | 'style' | 'template'
outputLang: string
defaultLang?: string
validExtensions?: string[]
exclude?: RegExp[]
}

const vueBlockLoader = (opts: BlockLoaderOptions): Loader => async (input, { loadFile }) => {
const contents = await input.getContents()

const BLOCK_RE = new RegExp(`<${opts.type}((\\s[^>\\s]*)*)>([\\S\\s.]*?)<\\/${opts.type}>`)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [scriptBlock, attrs = '', _lastAttr, script] = contents.match(/<script((\s[^>\s]*)*)>([\S\s.]*?)<\/script>/) || []
if (!scriptBlock || !script) {
const [block, attrs = '', _, blockContents] = contents.match(BLOCK_RE) || []

if (!block || !blockContents) {
return
}

// do not transpile `<script setup>`, #14
if (attrs.split(/\s+/g).includes('setup')) {
if (opts.exclude?.some(re => re.test(attrs))) {
return
}

const [, lang = 'js'] = attrs.match(/lang="([a-z]*)"/) || []
const [, lang = opts.outputLang] = attrs.match(/lang="([a-z]*)"/) || []
const extension = '.' + lang

const files = await loadFile({
getContents: () => script,
getContents: () => blockContents,
path: `${input.path}${extension}`,
srcPath: `${input.srcPath}${extension}`,
extension
}) || []

const scriptFile = files.find(f => ['.js', '.mjs'].includes(f.extension!))
if (!scriptFile) {
return
}
const blockOutputFile = files.find(f => f.extension === `.${opts.outputLang}` || opts.validExtensions?.includes(f.extension!))
if (!blockOutputFile) { return }

const newAttrs = attrs.replace(new RegExp(`\\s?lang="${lang}"`), '')

return [
{
path: input.path,
contents: contents.replace(scriptBlock, `<script${newAttrs}>\n${scriptFile.contents}</script>`)
contents: contents.replace(block, `<${opts.type}${newAttrs}>\n${blockOutputFile.contents?.trim()}\n</${opts.type}>`)
},
...files.filter(f => f !== scriptFile)
...files.filter(f => f !== blockOutputFile)
]
}

const sassLoader = vueBlockLoader({
outputLang: 'css',
type: 'style'
})

const scriptLoader = vueBlockLoader({
outputLang: 'js',
type: 'script',
exclude: [/\bsetup\b/],
validExtensions: ['.js', '.mjs']
})
8 changes: 8 additions & 0 deletions test/fixture/src/components/js.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@ export default {
})
}
</script>

<style lang="scss" scoped>
$color: red;
.test {
color: $color;
}
</style>
14 changes: 13 additions & 1 deletion test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { resolve } from 'pathe'
import { readFile } from 'fs-extra'
import { mkdist } from '../src/make'
import { createLoader } from '../src/loader'
import { jsLoader, vueLoader } from '../src/loaders'
import { jsLoader, sassLoader, vueLoader } from '../src/loaders'

describe('mkdist', () => {
it('mkdist', async () => {
Expand Down Expand Up @@ -96,6 +96,18 @@ describe('createLoader', () => {
expect(results).toMatchObject([{ contents: ['<script foo>', 'Test;', '</script>'].join('\n') }])
})

it('vueLoader handles style tags', async () => {
const { loadFile } = createLoader({
loaders: [vueLoader, sassLoader]
})
const results = await loadFile({
extension: '.vue',
getContents: () => '<style scoped lang="scss">$color: red; :root { background-color: $color }</style>',
path: 'test.vue'
})
expect(results).toMatchObject([{ contents: ['<style scoped>', ':root {', ' background-color: red;', '}', '</style>'].join('\n') }])
})

it('vueLoader bypass <script setup>', async () => {
const { loadFile } = createLoader({
loaders: [vueLoader, jsLoader]
Expand Down
68 changes: 63 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -999,7 +999,7 @@ ansi-styles@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==

anymatch@^3.0.3:
anymatch@^3.0.3, anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
Expand Down Expand Up @@ -1125,6 +1125,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==

binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==

brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
Expand All @@ -1133,7 +1138,7 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"

braces@^3.0.1:
braces@^3.0.1, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
Expand Down Expand Up @@ -1244,6 +1249,21 @@ char-regex@^1.0.2:
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==

"chokidar@>=3.0.0 <4.0.0":
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
glob-parent "~5.1.2"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.6.0"
optionalDependencies:
fsevents "~2.3.2"

ci-info@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
Expand Down Expand Up @@ -2437,7 +2457,7 @@ gitconfiglocal@^1.0.0:
dependencies:
ini "^1.3.2"

glob-parent@^5.1.2:
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
Expand Down Expand Up @@ -2618,6 +2638,11 @@ ignore@^5.1.1, ignore@^5.1.4, ignore@^5.1.8:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==

immutable@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==

import-fresh@^3.0.0, import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
Expand Down Expand Up @@ -2688,6 +2713,13 @@ is-bigint@^1.0.1:
dependencies:
has-bigints "^1.0.1"

is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"

is-boolean-object@^1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
Expand Down Expand Up @@ -2730,7 +2762,7 @@ is-generator-fn@^2.0.0:
resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==

is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
Expand Down Expand Up @@ -3749,7 +3781,7 @@ normalize-package-data@^3.0.0:
semver "^7.3.4"
validate-npm-package-license "^3.0.1"

normalize-path@^3.0.0:
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
Expand Down Expand Up @@ -3979,6 +4011,11 @@ picomatch@^2.0.4, picomatch@^2.2.2, picomatch@^2.2.3:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==

picomatch@^2.2.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==

pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
Expand Down Expand Up @@ -4141,6 +4178,13 @@ readable-stream@~2.3.6:
string_decoder "~1.1.1"
util-deprecate "~1.0.1"

readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"

redent@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
Expand Down Expand Up @@ -4270,6 +4314,15 @@ safe-regex@^2.1.1:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==

sass@^1.49.7:
version "1.49.7"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.7.tgz#22a86a50552b9b11f71404dfad1b9ff44c6b0c49"
integrity sha512-13dml55EMIR2rS4d/RDHHP0sXMY3+30e1TKsyXaSz3iLWVoDWEoboY8WzJd5JMnxrRHffKO3wq2mpJ0jxRJiEQ==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"

saxes@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
Expand Down Expand Up @@ -4385,6 +4438,11 @@ sort-package-json@^1.50.0:
is-plain-obj "2.1.0"
sort-object-keys "^1.1.3"

"source-map-js@>=0.6.2 <2.0.0":
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==

source-map-support@^0.5.6:
version "0.5.20"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9"
Expand Down

0 comments on commit 8f3a951

Please sign in to comment.