Skip to content

Commit

Permalink
Merge pull request #20 from es-repo/master
Browse files Browse the repository at this point in the history
CSS Modules support
closes #18
  • Loading branch information
jackmellis authored Sep 19, 2017
2 parents 3a5fc55 + fd6d3bd commit 9d68de3
Show file tree
Hide file tree
Showing 14 changed files with 2,458 additions and 377 deletions.
2,552 changes: 2,215 additions & 337 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
"dependencies": {
"consolidate": "^0.14.5",
"parse5": "^3.0.2",
"postcss": "^6.0.11",
"postcss-modules-sync": "^1.0.0",
"vue-template-compiler": "^2.2.0",
"vue-template-es2015-compiler": "^1.5.0"
},
Expand Down
10 changes: 10 additions & 0 deletions spec/css-module-default.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import test from 'ava';
import Vue from 'vue';
import component from './vue-files/css-module-default.vue';

test('import component with default CSS module', t => {
t.true(component.computed !== undefined);
t.true(component.computed.$style !== undefined);
const vm = new Vue(component).$mount();
t.is(vm.$style.color, '-style-color')
})
10 changes: 10 additions & 0 deletions spec/css-module-mixed.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import test from 'ava';
import Vue from 'vue';
import component from './vue-files/css-module-mixed.vue';

test('import component with mixed CSS modules', t => {
t.true(component.computed !== undefined);
t.deepEqual(Object.keys(component.computed), ['$style', 'green', 'blue']);
const vm = new Vue(component).$mount();
t.deepEqual([vm.$style.color, vm.green.color, vm.blue.color], ['-style-color', 'green-color', 'blue-color']);
})
10 changes: 10 additions & 0 deletions spec/css-module-named.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import test from 'ava';
import Vue from 'vue';
import component from './vue-files/css-module-named.vue';

test('import component with named CSS module', t => {
t.true(component.computed !== undefined);
t.true(component.computed.red !== undefined);
const vm = new Vue(component).$mount();
t.is(vm.red.color, 'red-color');
})
10 changes: 10 additions & 0 deletions spec/css-module-with-computed-props.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import test from 'ava';
import Vue from 'vue';
import component from './vue-files/css-module-with-computed-props.vue';

test('import component with named CSS module', t => {
t.true(component.computed !== undefined);
t.deepEqual(Object.keys(component.computed), ['message', '$style', 'green']);
const vm = new Vue(component).$mount();
t.deepEqual([vm.message, vm.$style.color, vm.green.color], ['I should be displayed in red.', '-style-color', 'green-color']);
})
7 changes: 7 additions & 0 deletions spec/external.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ test('imports component with external js', t => {
t.is(typeof c.data, 'function');
});

test('imports component with external css-module', t => {
t.is(c.name, 'external');
t.is(typeof c.computed, 'object');
t.is(typeof c.computed.$style, 'function');
t.is(c.computed.$style().color, '-style-color');
});

test('correctly renders component', t => {
let vm;
t.notThrows(() => vm = new Vue(c));
Expand Down
14 changes: 14 additions & 0 deletions spec/vue-files/css-module-default.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<template>
<div :class="$style.color">Default CSS module in red.</div>
</template>

<script>
module.exports = {
};
</script>

<style module>
.color {
color: red;
}
</style>
45 changes: 45 additions & 0 deletions spec/vue-files/css-module-mixed.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<template>
<div>
<div :class="$style.color">Default CSS-module in red.</div>
<div :class="green.color">Custom named CSS-module in green.</div>
<div :class="blue.color">Another one custom named CSS-module in blue.</div>
<div class="color">Scoped style in yellow.</div>
<div class="global-color">Global style in orange.</div>
</div>
</template>

<script>
module.exports = {
};
</script>

<style module>
.color {
color: red;
}
</style>

<style module="green">
.color {
color: green;
}
</style>

<style module="blue">
.color {
color: blue;
}
</style>

<style scoped>
.color {
color: yellow
}
<style>
<style>
.global-color {
color: orange
}
<style>
15 changes: 15 additions & 0 deletions spec/vue-files/css-module-named.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<div :class="red.color">Custom named CSS module in red.</div>
</template>

<script>
module.exports = {
};
</script>

<style module="red">
.color {
color: red;
}
</style>
29 changes: 29 additions & 0 deletions spec/vue-files/css-module-with-computed-props.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<template>
<div>
<div :class="$style.color">{{message}}</div>
<div :class="green.color">This should be green.</div>
</div>
</template>

<script>
module.exports = {
computed: {
message() {
return "I should be displayed in red."
}
}
};
</script>

<style module>
.color {
color: red;
}
</style>

<style module="green">
.color {
color: green;
}
</style>
3 changes: 3 additions & 0 deletions spec/vue-files/external/external-css.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.color {
color: red;
}
1 change: 1 addition & 0 deletions spec/vue-files/external/index.vue
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
<template src="./external-html.html"/>
<script src="./external-js.js"/>
<style src="./external-css.css" module/>
127 changes: 87 additions & 40 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,78 +1,125 @@
const compiler = require('vue-template-compiler');
const transpile = require('vue-template-es2015-compiler');
const {spawnSync} = require('child_process');
const {join, dirname} = require('path');
const {readFileSync} = require('fs');
const { spawnSync } = require('child_process');
const { join, dirname } = require('path');
const { readFileSync } = require('fs');
const postcss = require('postcss')
const postcssModules = require('postcss-modules-sync').default
const exportsTarget = '((module.exports.default || module.exports).options || module.exports.default || module.exports)';
const defaultConfig = {
transpileTemplates : true
transpileTemplates: true
};
let globalConfig = defaultConfig;

module.exports = function ({ content, filename, hook }) {

function retrieveContent(config) {
if (config.content){
if (config.content) {
return config.content;
}else if (config.attrs.src){
} else if (config.attrs.src) {
let fullPath = join(dirname(filename), config.attrs.src);
return readFileSync(fullPath, 'utf8');
}else{
} else {
return '';
}
}

const {template, script} = compiler.parseComponent(content, {pad : 'line'});
function retrieveAndTranspileContent(config, noTranspileLangs, transpileSpecial) {
let content = retrieveContent(config)

let lang = config.attrs.lang
if (lang) {
lang = lang.toLowerCase()
}

if(!script) {
throw new Error('Unable to read ' + filename + ': could not find a valid <script> tag');
if (lang && !noTranspileLangs.includes(lang)) {
content = transpileSpecial != null
? transpileSpecial(content, lang)
: hook(lang, content);
}
return content
}
let scriptPart = retrieveContent(script);
if (script.attrs.lang && !['js', 'javascript'].includes(script.attrs.lang.toLowerCase())){
scriptPart = hook(script.attrs.lang, scriptPart);

function getScriptPart(script) {
if (!script) {
throw new Error('Unable to read ' + filename + ': could not find a valid <script> tag');
}
return retrieveAndTranspileContent(script, ['js', 'javascript']);
}

// If there is a template then compile to render functions
// This avoids the need for a runtime compilation
// ES2015 template compiler to support es2015 features
let compiledTemplate = '';
if (template) {
let templatePart = retrieveContent(template);

let lang = template.attrs.lang;
if(lang && lang.toLowerCase() !== 'html') {
if (globalConfig.transpileTemplates){
const renderedResult = spawnSync(
process.execPath,
[
join(__dirname, './renderTemplate.js'),
lang,
templatePart
],
{encoding: 'utf-8'});
if(renderedResult.stderr) {
throw new Error(renderedResult.stderr);
}
templatePart = renderedResult.stdout;
}else{
templatePart = hook(lang, templatePart);
function getCompiledTemplate(template) {

function transpileTemplateSpecial(content, lang) {
const result = spawnSync(
process.execPath,
[
join(__dirname, './renderTemplate.js'),
lang,
content
],
{ encoding: 'utf-8' });

if (result.stderr) {
throw new Error(result.stderr);
}
return result.stdout;
}

const compiled = compiler.compile(templatePart, { preserveWhitespace: false });
if (!template)
return ''

// If there is a template then compile to render functions
// This avoids the need for a runtime compilation
// ES2015 template compiler to support es2015 features
let content = retrieveAndTranspileContent(template, ['html'],
globalConfig.transpileTemplates ? transpileTemplateSpecial : null);

const compiled = compiler.compile(content, { preserveWhitespace: false });
const renderFn = `function(){${compiled.render}}`;
const staticRenderFns = (compiled.staticRenderFns || [])
.map(fn => `function(){${fn}}`)
.join(',');
compiledTemplate = `\n;`
return `\n;`
+ transpile(`${exportsTarget}.render=${renderFn};`)
+ '\n'
+ transpile(`${exportsTarget}.staticRenderFns = [${staticRenderFns}];`)
+ `\n${exportsTarget}.render._withStripped = true;`;
}
const result = `${scriptPart}\n${compiledTemplate}`;

function getCssModuleComputedProps(styles) {
if (!styles)
return ''

let computedProps = []
for (let i = 0; i < styles.length; i++) {
const style = styles[i];
if (style.module !== undefined && style.module !== false) {
const moduleName = typeof style.module === 'string' ? style.module : '$style';
const content = retrieveAndTranspileContent(style, ['css']);
let cssClasses;
postcss(postcssModules(
{
generateScopedName: moduleName + '-[local]',
getJSON: json => { cssClasses = json; }
})).process(content).css;
const cssClassesStr = JSON.stringify(cssClasses)
computedProps.push(`${exportsTarget}.computed.${moduleName} = function(){ return ${cssClassesStr}; };`)
}
}
return computedProps.length > 0
? `${exportsTarget}.computed = ${exportsTarget}.computed || {};` + computedProps.join(' ')
: ''
}

const { template, script, styles } = compiler.parseComponent(content, { pad: 'line' });
const scriptPart = getScriptPart(script);
const compliledTemplate = getCompiledTemplate(template)
const cssModulesComputedProps = getCssModuleComputedProps(styles)

const result = `${scriptPart}\n${compliledTemplate}\n${cssModulesComputedProps}`;
return { content: result };
};

module.exports.configure = function (config) {
globalConfig = Object.assign({}, defaultConfig, config);
};

0 comments on commit 9d68de3

Please sign in to comment.