Skip to content

Commit e5adefc

Browse files
committed
init
1 parent 2c53ff6 commit e5adefc

21 files changed

+717
-1
lines changed

.babelrc

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"presets": [
3+
[
4+
"env",
5+
{
6+
"targets": {
7+
"node": 4
8+
}
9+
}
10+
]
11+
]
12+
}

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.DS_Store
2+
node_modules/
3+
lib
4+
npm-debug.log*
5+
coverage
6+
.nyc_output

.travis.yml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
language: node_js
2+
node_js:
3+
- "4"
4+
- "5"
5+
- "6"
6+
install:
7+
- npm install
8+
script:
9+
- npm test

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Change History
2+
==============
3+
4+
v1.0.0
5+
---
6+
7+
* 支持传入变量文件,转换成目标预处理器语法的变量,自动注入组件单文件中

README.md

+148-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,148 @@
1-
# theme-variables-loader
1+
# vue-style-variables-loader
2+
3+
这个基于 webpack 的插件试图解决使用 Vue 开发中的两个问题:
4+
1. 在 Vue 单文件中自动引入变量文件
5+
2. 选用了一个 UI 框架并使用了框架提供的主题解决方案,在组件中想使用这些主题变量,但又不想使用框架指定的预处理器
6+
7+
## 问题1:在 Vue 单文件中引入变量文件
8+
9+
通常我们的项目中包含一个定义了常用变量的文件,以开发选择的预处理器格式存在。
10+
使用时,在每个 Vue 单文件组件的 style 块中都需要手动引入这个变量文件。虽然使用 webpack alias 之后不用考虑路径问题,但如果能自动引入将方便很多。
11+
``` vue
12+
// Component.vue
13+
14+
<style lang="scss">
15+
// 引入变量文件
16+
@import "@/styles/variables.scss";
17+
// 开始使用变量
18+
</style>
19+
```
20+
21+
有人针对这个问题向 vue-loader 提出了[相关 issue](https://github.com/vuejs/vue-loader/issues/328)。而 vue-loader 认为这个工作应该交给各个预处理器 loader 完成。
22+
23+
例如使用 sass 时,可以[使用 sass-resources-loader](https://vue-loader.vuejs.org/en/configurations/pre-processors.html)
24+
``` javascript
25+
{
26+
loader: 'sass-resources-loader',
27+
options: {
28+
resources: 'variables.scss'
29+
}
30+
}
31+
```
32+
33+
或者[使用 sass-loader](https://github.com/webpack-contrib/sass-loader#environment-variables)的注入环境变量功能。[keen-ui就采用了这种方式支持用户覆盖预定义的主题变量](https://github.com/JosephusPaye/Keen-UI/blob/master/Customization.md#customization)
34+
``` javascript
35+
plugins: [
36+
new webpack.LoaderOptionsPlugin({
37+
options: {
38+
sassLoader: {
39+
data: '@import "src/styles/variables.scss";',
40+
includePaths: 'src/styles'
41+
},
42+
context: path.resolve(__dirname) // your project root
43+
}
44+
})
45+
]
46+
```
47+
48+
而在 stylus-loader 中,可以使用[import](https://github.com/shama/stylus-loader#using-nib-with-stylus)达到引入全局变量的目的。
49+
``` javascript
50+
plugins: [
51+
new webpack.LoaderOptionsPlugin({
52+
test: /\.styl$/,
53+
stylus: {
54+
use: [require('nib')()],
55+
import: ['~nib/lib/nib/index.styl']
56+
}
57+
})
58+
]
59+
```
60+
61+
less-loader 中并没有找到解决方法,似乎只能每次手动引入了。
62+
63+
可以看出,各个预处理器 loader 都有自己的方式解决这个问题。而且就算是手动引入,代价也并不高,让我们继续来看第二个问题。
64+
65+
## 问题2:选用和 UI 框架不同的预处理器开发
66+
67+
各个 UI 框架都有自己的主题解决方案,例如:
68+
* vuetify [使用 stylus hash 变量](https://vuetifyjs.com/style/theme)覆盖[预定义的变量列表](https://github.com/vuetifyjs/vuetify/blob/dev/src/stylus/settings/_theme.styl)
69+
* keen-ui [使用用户自定义的 sass 变量文件](https://github.com/JosephusPaye/Keen-UI/blob/master/Customization.md#customization)覆盖[预定义的变量列表](https://github.com/JosephusPaye/Keen-UI/blob/master/src/styles/variables.scss)
70+
* vux 通过 vux-loader [使用用户自定义的 less 变量文件](https://vux.li/#/?id=%E9%A2%9C%E8%89%B2%E9%85%8D%E7%BD%AE)覆盖[预定义的变量列表](https://github.com/airyland/vux/blob/v2/src/styles/variable.less)
71+
* [vue-material](https://github.com/vuematerial/vue-material)和以上框架都不同,使用了[编程式方式设置主题](http://vuematerial.io/#/themes/configuration)
72+
73+
可以看出在使用变量文件覆盖的方案中,主题变量必须使用框架指定的预处理器定义。
74+
例如选择了 vuetify,那开发者自定义的变量文件就必须使用 stylus 来写。这样在实际组件开发中,如果选择 less,就无法使用 stylus 定义的这些变量了。
75+
``` vue
76+
// Component.vue
77+
78+
<style lang="less">
79+
// 引入变量文件
80+
@import "@/styles/variables.styl";
81+
// 出错了,less 并不认识 stylus 定义的变量 $bg-color
82+
background: @bg-color;
83+
</style>
84+
```
85+
86+
所以在上述场景中,我们需要将文件中使用 stylus 定义的每一个主题变量都转换成 less 变量,然后注入`.vue`文件的`<style>`块中。
87+
如果能实现这一点,其实第一个问题也就顺便解决了。
88+
89+
## 实现思路
90+
91+
首先,开发者使用所选 UI 框架的主题解决方案,使用框架指定的预处理器创建一个变量文件,由于该文件只包含变量,对于开发者而言,学习特定预处理器语法的成本并不高。
92+
93+
然后,使用 loader 处理每一个`.vue`文件。该 loader 接受之前的变量文件作为输入,在每个`.vue`文件的每个`<style>`块中,根据当前`<style>`块指定的预处理器语言,将包含的所有变量进行转换并注入。这样开发者就可以直接使用自己熟悉的预处理器语法开发了。
94+
95+
并不需要做类似[stylus,less,sass之间全部语法的互相转换](http://csspre.com/convert/)。只需要变量声明这部分。
96+
97+
*WIP* 但也并不能使用正则简单替换,原因是 stylus 中有 hash 类型的变量声明:
98+
``` stylus
99+
$theme := {
100+
primary: 'white';
101+
}
102+
```
103+
104+
所以只能写一个简单的 compiler 做一些词法语法分析的工作。
105+
106+
## 使用方法
107+
108+
安装
109+
```bash
110+
npm install theme-variables-loader --save-dev
111+
```
112+
113+
在 webpack 中配置规则,处理项目中每一个`.vue`文件,要注意配置`include/exclude`规则,使 loader 只作用于开发者项目内的文件,不包含第三方文件。
114+
```javascript
115+
{
116+
test: /\.vue$/,
117+
use: [
118+
{
119+
loader: 'vue-loader',
120+
options: vueLoaderConfig
121+
},
122+
{
123+
loader: 'theme-variables-loader',
124+
options: {
125+
injectInVueFile: true
126+
}
127+
}
128+
],
129+
include: [resolve('src')]
130+
},
131+
{
132+
test: /\.vue$/,
133+
use: [
134+
{
135+
loader: 'vue-loader',
136+
options: vueLoaderConfig
137+
}
138+
],
139+
exclude: [resolve('src')]
140+
},
141+
```
142+
143+
## 参数说明
144+
145+
暂定两个参数:
146+
* `variablesFiles` 变量文件路径,Array 类型
147+
* `imports` import 语句,Array 类型
148+

examples/index.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @file examples index
3+
* @author panyuqi ([email protected])
4+
*/
5+
6+
/* eslint-disable fecs-no-require */
7+
8+
const fs = require('fs');
9+
const path = require('path');
10+
const rimraf = require('rimraf');
11+
12+
cleanAllExamples();
13+
14+
function getExamples() {
15+
return fs.readdirSync(__dirname)
16+
.filter(readdirItem => fs.statSync(path.join(__dirname, readdirItem)).isDirectory());
17+
}
18+
19+
function cleanAllExamples() {
20+
let examplesDistPathNames = getExamples()
21+
.map(exampleName => path.join(__dirname, exampleName, 'dist'));
22+
23+
for (let path of examplesDistPathNames) {
24+
fs.stat(path, (err, stat) => {
25+
if (err) {
26+
return;
27+
}
28+
if (stat.isDirectory()) {
29+
rimraf.sync(path);
30+
}
31+
});
32+
}
33+
}
34+
35+
exports.getExamples = getExamples;
36+
exports.cleanAllExamples = cleanAllExamples;

examples/simple/index.html

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!DOCTYPE html>
2+
<html lang="zh_CN">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>title</title>
6+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
7+
<meta name="viewport" content="width=device-width, initial-scale=1">
8+
<% for (var jsFilePath of htmlWebpackPlugin.files.js) { %>
9+
<link rel="preload" href="<%= jsFilePath %>" as="script">
10+
<% } %>
11+
<% for (var cssFilePath of htmlWebpackPlugin.files.css) { %>
12+
<link rel="preload" href="<%= cssFilePath %>" as="style">
13+
<% } %>
14+
</head>
15+
<body>
16+
<div id="app"></div>
17+
<!-- built files will be auto injected -->
18+
</body>
19+
</html>

examples/simple/src/Component.vue

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<template>
2+
<div class="component-wrapper">
3+
</div>
4+
</template>
5+
6+
<script>
7+
8+
export default {
9+
name: 'component'
10+
};
11+
</script>
12+
13+
<style lang="styl" scoped>
14+
15+
.component-wrapper
16+
height: 52px;
17+
background: blue;
18+
19+
</style>

examples/simple/src/entry.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @file simple entry
3+
* @author panyuqi <[email protected]>
4+
*/
5+
6+
import Vue from 'vue';
7+
import Component from './Component';
8+
9+
export default new Vue({
10+
components: {
11+
Component
12+
},
13+
template: '<component />'
14+
});

examples/simple/src/styles/variables.less

Whitespace-only changes.

examples/simple/src/styles/variables.scss

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
$test-variable := blue;

examples/simple/utils.js

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* @file utils
3+
* @author panyuqi ([email protected])
4+
*/
5+
6+
'use strict';
7+
8+
const path = require('path');
9+
const ExtractTextPlugin = require('extract-text-webpack-plugin');
10+
11+
exports.assetsPath = function (newPath) {
12+
return path.posix.join('static', newPath);
13+
};
14+
15+
exports.cssLoaders = function (options) {
16+
options = options || {};
17+
18+
let cssLoader = {
19+
loader: 'css-loader',
20+
options: {
21+
minimize: process.env.NODE_ENV === 'production',
22+
sourceMap: options.sourceMap
23+
}
24+
};
25+
26+
// generate loader string to be used with extract text plugin
27+
function generateLoaders(loader, loaderOptions) {
28+
let loaders = [cssLoader];
29+
if (loader) {
30+
loaders.push({
31+
loader: loader + '-loader',
32+
options: Object.assign({}, loaderOptions, {
33+
sourceMap: options.sourceMap
34+
})
35+
});
36+
}
37+
38+
// Extract CSS when that option is specified
39+
// (which is the case during production build)
40+
if (options.extract) {
41+
return ExtractTextPlugin.extract({
42+
use: loaders,
43+
fallback: 'vue-style-loader'
44+
});
45+
}
46+
47+
return ['vue-style-loader'].concat(loaders);
48+
}
49+
50+
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
51+
return {
52+
css: generateLoaders(),
53+
postcss: generateLoaders(),
54+
less: generateLoaders('less'),
55+
sass: generateLoaders('sass', {indentedSyntax: true}),
56+
scss: generateLoaders('sass'),
57+
stylus: generateLoaders('stylus'),
58+
styl: generateLoaders('stylus')
59+
};
60+
};
61+
62+
// Generate loaders for standalone style files (outside of .vue)
63+
exports.styleLoaders = function (options) {
64+
let output = [];
65+
let loaders = exports.cssLoaders(options);
66+
67+
Object.keys(loaders).forEach(function (extension) {
68+
output.push({
69+
test: new RegExp('\\.' + extension + '$'),
70+
use: loaders[extension]
71+
});
72+
});
73+
return output;
74+
};

examples/simple/vue-loader.conf.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* @file vue-loader conf
3+
* @author panyuqi ([email protected])
4+
*/
5+
6+
'use strict';
7+
8+
const utils = require('./utils');
9+
10+
module.exports = {
11+
loaders: utils.cssLoaders({
12+
sourceMap: false,
13+
extract: true
14+
})
15+
};

0 commit comments

Comments
 (0)