-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e446c5b
Showing
14 changed files
with
4,734 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.DS_Store | ||
node_modules/ | ||
dist/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<template> | ||
<div> | ||
<h1>{{text}}</h1> | ||
I have two style blocks:<br> | ||
one scoped, that makes my background pink<br> | ||
and the other is not,<br> | ||
it makes all headers' <code>font-family</code> <code>sans-serif</code> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
export default { | ||
name: 'DeeperTwo', | ||
data() { | ||
return { | ||
text: 'Hello from a level deeper Component!' | ||
} | ||
}, | ||
} | ||
</script> | ||
|
||
<style scoped> | ||
div { | ||
background: pink; | ||
} | ||
</style> | ||
|
||
<style> | ||
h1 { | ||
font-family: sans-serif; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<template> | ||
<div class="container"> | ||
<h1>{{text}}</h1> | ||
I have class <code>.container</code><br> | ||
with a silver background defined in scoped styles<br> | ||
<br> | ||
I also host two other components: | ||
<Two /> | ||
<DeeperTwo /> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import Two from './two.vue' | ||
import DeeperTwo from './depth/two.vue' | ||
export default { | ||
name: 'One', | ||
components: { Two, DeeperTwo }, | ||
data() { | ||
return { | ||
text: 'Hello from component One' | ||
} | ||
}, | ||
} | ||
</script> | ||
|
||
<style scoped> | ||
.container { | ||
background: silver; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<template> | ||
</template> | ||
|
||
<script> | ||
export default { | ||
name: 'Two', | ||
template: `<div class="container"> | ||
<h1>{{text}}</h1> | ||
I have template defined inside script<br> | ||
with <code>template: \` ... \`</code><br> | ||
<span>This span is colored purple with scoped styles</span><br> | ||
<br> | ||
I also have a div with class <code>.container</code><br> | ||
But its color is gold, because my scoped styles said so<br> | ||
<br> | ||
My styles are also in SCSS by the way | ||
</div>` | ||
data() { | ||
return { | ||
text: 'Hello from component Two!' | ||
} | ||
}, | ||
} | ||
</script> | ||
|
||
<style scoped lang="scss"> | ||
.container { | ||
background: gold; | ||
h1 { | ||
color: white; | ||
&:hover { | ||
background: black; | ||
} | ||
} | ||
} | ||
span { | ||
color: purple; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
#!/usr/bin/env node | ||
|
||
const glob = require('glob') | ||
const fs = require('fs') | ||
const compiler = require('vue-template-compiler') | ||
const sass = require('node-sass') | ||
const postcss = require('postcss') | ||
const path = require('path') | ||
const minimist = require('minimist') | ||
const htmlparser = require('node-html-parser') | ||
|
||
|
||
// options | ||
|
||
const args = minimist(process.argv.slice(2)) | ||
const opts = { | ||
tab: ' ', | ||
ignore: ['node_modules/**'], | ||
noscope: false, | ||
dest: 'dist/', | ||
} | ||
|
||
opts.tab = args.tab != undefined ? args.tab : opts.tab | ||
opts.ignore = args.ignore ? opts.ignore.concat(args.ignore.split(',')) : opts.ignore | ||
opts.dest = args.dest | ||
? args.dest.slice(-1) != '/' | ||
? args.dest+'/' | ||
: args.dest | ||
: opts.dest | ||
opts.noscope = args.noscope ? true : false | ||
|
||
|
||
|
||
const files = glob.sync('**/*.vue', { ignore: opts.ignore }) | ||
|
||
|
||
files.forEach(file => { | ||
|
||
const pathParse = path.parse(file) | ||
|
||
const scopeSelectors = [] | ||
|
||
let warnings = [] | ||
|
||
fs.readFile(file, 'utf8', async (err, content) => { | ||
|
||
if (err) return console.error(err) | ||
|
||
|
||
// parse | ||
|
||
const parsed = compiler.parseComponent(content) | ||
|
||
|
||
// extract | ||
|
||
|
||
// script & style | ||
|
||
let script = parsed.script ? parsed.script.content.trim() : '' | ||
let styles = '' // styles will be filled later | ||
|
||
// template | ||
|
||
let template = '' | ||
if (parsed.template && parsed.template.content.trim()) { | ||
template = parsed.template.content.trim() | ||
} | ||
else if (script) { | ||
// there is no template block, but there is script | ||
// maybe template is in js | ||
const match = script.match(/(.|\n)*template:\s*([\`'"])/m) | ||
if (match) { | ||
// template is in js | ||
// extract it | ||
template = script.replace(match[0], '') | ||
const quote = match[0].slice(-1) | ||
let regex = new RegExp('(?<!\\\\)\\'+quote+'(.|\\n)*','m') | ||
template = template.replace(regex, '') | ||
|
||
// remove template from script (it will get injected back later) | ||
regex = new RegExp('template:\\s*'+quote+'(.|\\n)*(?<!\\\\)'+quote+',?\\s*', 'm') | ||
script = script.replace(regex, '') | ||
} | ||
} | ||
|
||
|
||
// process | ||
|
||
|
||
// styles | ||
|
||
if (parsed.styles && parsed.styles.length) { | ||
|
||
for (let parsedStyle of parsed.styles) { | ||
let style = parsedStyle.content.trim() | ||
|
||
if (parsedStyle.lang && parsedStyle.lang == 'scss') { | ||
// scss -> css | ||
style = sass.renderSync({ | ||
data: style, | ||
outputStyle: 'expanded' | ||
}).css.toString() | ||
} | ||
|
||
|
||
if (parsedStyle.scoped && template && !opts.noscope) { | ||
// scoped | ||
|
||
// generate scope name from filename | ||
const scope = file | ||
.replace(/\.vue$/, '') | ||
.replace(/[\/.]/g, '-') | ||
|
||
|
||
// add scope to css selectors | ||
style = await postcss().use(root => { | ||
root.walkRules(rule => { | ||
scopeSelectors.push(rule.selector) | ||
const selectors = rule.selector.match(/(^|[\.#\s])[\w\-]+/g) | ||
selectors.forEach(s => { | ||
rule.selector = rule.selector.replace(s, s+'[data-scope-'+scope+']') | ||
}) | ||
}) | ||
}).process(style, { from: undefined }).then(result => result.css) | ||
|
||
|
||
// add scope to template items | ||
|
||
const html = htmlparser.parse(template) | ||
|
||
scopeSelectors.forEach(selector => { | ||
// only add scope attribute to elements that are styled from scoped css | ||
const els = html.querySelectorAll(selector) | ||
|
||
for (let el of els) { | ||
for (let a of Object.keys(el.attributes)) { | ||
if (a.includes('data-scope-')) { | ||
el.removeAttribute(a) | ||
} | ||
} | ||
el.setAttribute('data-scope-'+scope, '') | ||
} | ||
|
||
}) | ||
|
||
template = html.toString() | ||
|
||
// remove ="" for prettiness | ||
const regex = new RegExp('(data-scope-'+scope+')=""','g') | ||
template = template.replace(regex, '$1') | ||
|
||
} else if (!template && !opts.noscope) { | ||
warnings.push('scoped style, but no template') | ||
} | ||
|
||
// join all styles | ||
styles += style + '\n' | ||
|
||
} | ||
|
||
if (script) { | ||
// add style attachment to script | ||
script += '\n\n// attach styles\nfetch(\''+pathParse.dir+'/'+pathParse.name+'.css\').then(res => res.text()).then(style => document.head.insertAdjacentHTML(\'beforeend\', \'<style>\'+style+\'</style>\'))' | ||
} | ||
|
||
} else { | ||
warnings.push('no style') | ||
} | ||
|
||
|
||
// template -> script | ||
|
||
if (template && script) { | ||
|
||
// backslash any unbackslashed occurancies of ` | ||
template = template.trim().replace(/(?<!\\)`/g, '\`') | ||
|
||
const templateString = 'template: `\n' + template + '`,' | ||
|
||
const match = script.match(/(export\sdefault\s*\{)/) | ||
if (match) { | ||
// there is a default export, append template to it | ||
script = script.replace( | ||
match[0], match[0] + '\n' + opts.tab + templateString | ||
) | ||
} else { | ||
warnings.push('can\'t find default export') | ||
} | ||
|
||
} else { | ||
warnings.push('no script or template') | ||
} | ||
|
||
|
||
// rewrite imports | ||
if (script) { | ||
script = script.replace(/import(.*)\.vue(['"])/g, 'import$1.js$2') | ||
} | ||
|
||
|
||
// write files | ||
|
||
const dir = opts.dest + pathParse.dir | ||
const outPath = dir + '/' + pathParse.name | ||
|
||
fs.mkdir(dir, { recursive: true }, (err) => { | ||
if (err) return console.log(err) | ||
|
||
if (script) { | ||
fs.writeFile(outPath+'.js', script, err => { | ||
if (err) return console.log(err) | ||
}) | ||
} | ||
|
||
if (styles) { | ||
fs.writeFile(outPath+'.css', styles, err => { | ||
if (err) return console.log(err) | ||
}) | ||
} | ||
}) | ||
|
||
|
||
// log warnings | ||
|
||
if (warnings.length) { | ||
warnings.forEach(w => console.log('\x1b[33m%s\x1b[0m', 'WARN! '+file+' : '+ w)) | ||
} | ||
|
||
|
||
|
||
}) | ||
}) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
div[data-scope-components-depth-two] { | ||
background: pink; | ||
} | ||
h1 { | ||
font-family: sans-serif; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
export default { | ||
template: ` | ||
<div data-scope-components-depth-two> | ||
<h1>{{text}}</h1> | ||
I have two style blocks:<br> | ||
one scoped, that makes my background pink<br> | ||
and the other is not,<br> | ||
it makes all headers' <code>font-family</code> <code>sans-serif</code> | ||
</div>`, | ||
name: 'DeeperTwo', | ||
data() { | ||
return { | ||
text: 'Hello from a level deeper Component!' | ||
} | ||
}, | ||
} | ||
|
||
// attach styles | ||
fetch('components/depth/two.css').then(res => res.text()).then(style => document.head.insertAdjacentHTML('beforeend', '<style>'+style+'</style>')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.container[data-scope-components-one] { | ||
background: silver; | ||
} |
Oops, something went wrong.