├── CHANGELOG.md
├── example
└── src
│ └── page
│ ├── index
│ ├── container
│ │ ├── index.less
│ │ └── index.js
│ ├── main.html
│ └── main.js
│ └── pindex
│ ├── container
│ ├── index.less
│ └── index.js
│ ├── main.js
│ └── main.html
├── .stylelintrc.js
├── postcss.config.js
├── src
├── index.less
├── pindex.js
├── index.js
└── spin.js
├── .npmignore
├── config
├── steamer.config.js
└── project.js
├── .gitignore
├── test
└── unit
│ ├── helpers
│ └── enzyme.helper.js
│ ├── specs
│ └── spin.spec.js
│ ├── index.js
│ └── karma.conf.js
├── .eslintrc.js
├── tools
├── feature
│ ├── tsconfig.json
│ └── feature.js
├── rules
│ ├── fileRules.js
│ ├── htmlRules.js
│ ├── jsRules.js
│ └── styleRules.js
├── plugins
│ ├── basePlugins.js
│ └── resourcePlugins.js
├── optimization
│ └── index.js
├── script.js
├── server.js
└── webpack.base.js
├── .editorconfig
├── .babelrc
├── jest.conf.json
├── .steamer
└── steamer-react-component.js
├── LICENSE
├── README.md
└── package.json
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 3.0.1
2 | - update dependency
3 |
--------------------------------------------------------------------------------
/example/src/page/index/container/index.less:
--------------------------------------------------------------------------------
1 | .hello {
2 | font-size: 28px;
3 | }
--------------------------------------------------------------------------------
/example/src/page/pindex/container/index.less:
--------------------------------------------------------------------------------
1 | .hello {
2 | font-size: 28px;
3 | }
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": "@alloyteam/stylelint-config-standard",
3 | }
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('postcss-import')(),
4 | require('autoprefixer')()
5 | ]
6 | }
--------------------------------------------------------------------------------
/src/index.less:
--------------------------------------------------------------------------------
1 | #spin {
2 | position: fixed;
3 | top: 0;
4 | bottom: 0;
5 | left: 0;
6 | right: 0;
7 | margin: auto;
8 | }
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | node_modules.7z
3 | .DS_Store
4 | .webpack_cache
5 | .cache
6 | .happypack
7 | npm-debug.log
8 | yarn-error.log
9 | coverage
--------------------------------------------------------------------------------
/config/steamer.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "webserver": "//localhost:9000/",
3 | "cdn": "//localhost:8000/",
4 | "port": "9000",
5 | "route": "/",
6 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | node_modules.7z
3 | .svn
4 | .DS_Store
5 | .cache
6 | .happypack
7 | npm-debug.log
8 | yarn-error.log
9 | jest
10 | dist
11 | test/**/coverage
12 | dev
13 |
--------------------------------------------------------------------------------
/test/unit/helpers/enzyme.helper.js:
--------------------------------------------------------------------------------
1 | // ES6 Import
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-15';
4 |
5 | Enzyme.configure({ adapter: new Adapter() });
6 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {},
3 | "extends": [
4 | "eslint-config-alloy/react",
5 | ],
6 | "plugins": [],
7 | "rules": {
8 | 'one-var': 'off'
9 | },
10 | "globals": {}
11 | };
--------------------------------------------------------------------------------
/tools/feature/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | "sourceMap": true,
5 | "noImplicitAny": true,
6 | "module": "commonjs",
7 | "target": "es5",
8 | "jsx": "react"
9 | },
10 | "include": ["./src/**/*"]
11 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [{package.json,.travis.yml}]
15 | indent_style = space
16 | indent_size = 2
--------------------------------------------------------------------------------
/example/src/page/pindex/main.js:
--------------------------------------------------------------------------------
1 | /** @jsx h */
2 | import Preact, { h, Component } from 'preact';
3 |
4 | import Main from './container/index';
5 |
6 | const rootEl = document.getElementById('root');
7 |
8 | const render = (Component) => {
9 | ReactDOM.render(
10 | ,
11 | rootEl
12 | );
13 | };
14 |
15 | render(Main);
--------------------------------------------------------------------------------
/example/src/page/index/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | react component
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/src/page/pindex/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | react component
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "transform-decorators-legacy"
4 | ],
5 | "presets": [
6 | ["es2015", {"loose": true, "modules": false}],
7 | "react"
8 | ],
9 | "env": {
10 | "test": {
11 | "presets": [
12 | ["es2015", {"loose": true, "modules": false}],
13 | "react",
14 | "airbnb"
15 | ]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/example/src/page/index/container/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Spinner from 'index';
3 |
4 | import './index.less';
5 |
6 | class Main extends Component {
7 |
8 | constructor(props, context) {
9 | super(props, context);
10 | }
11 |
12 | render() {
13 |
14 | return (
15 |
16 | );
17 | }
18 | }
19 |
20 | export default Main;
--------------------------------------------------------------------------------
/test/unit/specs/spin.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | import Spin from '../../../src/index.js';
4 | import React from 'react';
5 | // import { expect } from 'chai';
6 | import { mount, shallow } from 'enzyme';
7 |
8 | describe('test', () => {
9 |
10 | it('spin', function() {
11 | const wrapper = mount();
12 | expect(wrapper.find('div')).to.have.length(1);
13 | });
14 |
15 | });
16 |
--------------------------------------------------------------------------------
/example/src/page/pindex/container/index.js:
--------------------------------------------------------------------------------
1 | /** @jsx h */
2 | import Preact, { h, Component } from 'preact';
3 | import Spinner from 'index';
4 |
5 | import './index.less';
6 |
7 | class Main extends Component {
8 |
9 | constructor(props, context) {
10 | super(props, context);
11 | }
12 |
13 | render() {
14 |
15 | return (
16 |
17 | );
18 | }
19 | }
20 |
21 | export default Main;
--------------------------------------------------------------------------------
/example/src/page/index/main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { AppContainer } from 'react-hot-loader';
4 |
5 | import Main from './container/index';
6 |
7 | const rootEl = document.getElementById('root');
8 |
9 | const render = (Component) => {
10 | ReactDOM.render(
11 |
12 |
13 | ,
14 | rootEl
15 | );
16 | };
17 |
18 | render(Main);
19 |
20 | // Hot Module Replacement API
21 | if (module.hot) {
22 | module.hot.accept('./container/index', () => {
23 | const NextApp = require('./container/index').default;
24 | render(NextApp);
25 | });
26 | }
--------------------------------------------------------------------------------
/test/unit/index.js:
--------------------------------------------------------------------------------
1 | // Polyfill fn.bind() for PhantomJS
2 | /* eslint-disable no-extend-native */
3 | Function.prototype.bind = require('function-bind');
4 |
5 | // 引入所有测试文件 (以 .spec.js 结尾)
6 | const testsContext = require.context('./specs', true, /\.spec$/);
7 | testsContext.keys().forEach(testsContext);
8 |
9 | // 引入除了main.js的所有源文件以做覆盖率测试
10 | // 你也可以修改配置只测试一部分js文件
11 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/);
12 | srcContext.keys().forEach(srcContext);
13 |
14 | // 引入enyzme 需要用的helper,参考
15 | // http://airbnb.io/enzyme/docs/installation/index.html
16 | // https://github.com/airbnb/enzyme/issues/1257
17 | var testHelpers = require.context('./helpers');
18 | testHelpers.keys().forEach(testHelpers);
19 |
--------------------------------------------------------------------------------
/jest.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": [
3 | "js",
4 | "jsx"
5 | ],
6 | "moduleDirectories": [
7 | "node_modules"
8 | ],
9 | "coverageDirectory": "/test/jest/coverage",
10 | "testMatch": [
11 | "**/test/jest/**/?(*.)(spec|test).js?(x)"
12 | ],
13 | "setupFiles": [
14 | "/test/jest/helpers/enzyme.helper.js"
15 | ],
16 | "moduleNameMapper": {
17 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/test/jest/mocks/fileMock.js",
18 | "\\.(css|less|scss|sass|styl)$": "/test/jest/mocks/styleMock.js"
19 | },
20 | "unmockedModulePathPatterns": [
21 | "node_modules/react/",
22 | "node_modules/enzyme/"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/.steamer/steamer-react-component.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | files: [
3 | "src",
4 | "tools",
5 | "config",
6 | "README.md",
7 | ".eslintrc.js",
8 | ".eslintignore",
9 | ".stylelintrc.js",
10 | "postcss.config.js",
11 | "jest.conf.json",
12 | ".gitignore",
13 | ".babelrc",
14 | "example",
15 | ".editorconfig"
16 | ],
17 | options: [
18 | {
19 | type: 'input',
20 | name: 'webserver',
21 | message: 'html url(//localhost:9000/)',
22 | default: "//localhost:9000/",
23 | },
24 | {
25 | type: 'input',
26 | name: 'port',
27 | message: 'development server port(9000)',
28 | default: '9000',
29 | }
30 | ]
31 | };
32 |
--------------------------------------------------------------------------------
/tools/rules/fileRules.js:
--------------------------------------------------------------------------------
1 | `use strict`;
2 |
3 |
4 |
5 | module.exports = function(config) {
6 |
7 | var configWebpack = config.webpack,
8 | isProduction = config.env === 'production';
9 |
10 | var rules = [
11 | {
12 | test: /\.ico$/,
13 | loader: 'url-loader',
14 | options: {
15 | name: '[name].[ext]'
16 | }
17 | },
18 | {
19 | test: /\.(jpe?g|png|gif|svg)$/i,
20 | loaders: [
21 | {
22 | loader: 'url-loader',
23 | options: {
24 | publicPath: isProduction ? configWebpack.imgCdn : configWebpack.webserver,
25 | limit: 1000,
26 | name: 'img/[path]/[name].[ext]'
27 | }
28 | }
29 | ]
30 | }
31 | ];
32 |
33 | return rules;
34 | };
--------------------------------------------------------------------------------
/tools/plugins/basePlugins.js:
--------------------------------------------------------------------------------
1 | `use strict`;
2 |
3 | const path = require('path'),
4 | os = require('os');
5 |
6 | var UglifyJSPlugin = require('uglifyjs-webpack-plugin');
7 |
8 | module.exports = function(config, webpack) {
9 |
10 | var configWebpack = config.webpack,
11 | isProduction = config.env === 'production';
12 |
13 | var plugins = [
14 | new webpack.NoEmitOnErrorsPlugin(),
15 | new webpack.DefinePlugin(configWebpack.injectVar),
16 | // new webpack.optimize.ModuleConcatenationPlugin()
17 | ];
18 |
19 | if (isProduction) {
20 |
21 | if (configWebpack.compress) {
22 | plugins.push(new UglifyJSPlugin({
23 | parallel: {
24 | cache: true,
25 | workers: os.cpus().length,
26 | },
27 | warnings: true,
28 | }));
29 | }
30 | }
31 | else {
32 | plugins.push(new webpack.HotModuleReplacementPlugin());
33 | }
34 |
35 | return plugins;
36 | };
--------------------------------------------------------------------------------
/tools/optimization/index.js:
--------------------------------------------------------------------------------
1 | let UglifyJsPlugin = require('uglifyjs-webpack-plugin');
2 | let OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
3 |
4 | module.exports = function(config, webpack) {
5 | let configWebpack = config.webpack;
6 | let optimization = {
7 | minimizer: []
8 | };
9 |
10 | if (configWebpack.compress) {
11 | optimization.minimizer.push(new UglifyJsPlugin({
12 | cache: true,
13 | parallel: true,
14 | sourceMap: true // set to true if you want JS source maps
15 | }));
16 | optimization.minimizer.push(
17 | new OptimizeCSSAssetsPlugin({
18 | cssProcessor: require('cssnano'),
19 | cssProcessorOptions: {
20 | reduceIdents: false,
21 | autoprefixer: false,
22 | },
23 | })
24 | );
25 | }
26 |
27 | return optimization;
28 | };
29 |
--------------------------------------------------------------------------------
/tools/rules/htmlRules.js:
--------------------------------------------------------------------------------
1 | `use strict`;
2 |
3 |
4 |
5 | module.exports = function(config) {
6 |
7 | var configWebpack = config.webpack;
8 |
9 | // 模板loader
10 | const templateRules = {
11 | html: {
12 | test: /\.html$/,
13 | loader: 'html-loader'
14 | },
15 | pug: {
16 | test: /\.pug$/,
17 | loader: 'pug-loader'
18 | },
19 | handlebars: {
20 | test: /\.handlebars$/,
21 | loader: 'handlebars-loader'
22 | },
23 | ejs: {
24 | test: /\.ejs$/,
25 | loader: 'ejs-compiled-loader',
26 | query: {
27 | 'htmlmin': true, // or enable here
28 | 'htmlminOptions': {
29 | removeComments: true
30 | }
31 | }
32 | }
33 | };
34 |
35 | var rules = [];
36 |
37 | configWebpack.template.forEach((tpl) => {
38 | let rule = templateRules[tpl] || '';
39 | rule && rules.push(rule);
40 | });
41 |
42 | return rules;
43 | };
--------------------------------------------------------------------------------
/tools/rules/jsRules.js:
--------------------------------------------------------------------------------
1 | `use strict`;
2 |
3 | const path = require('path');
4 |
5 | module.exports = function(config) {
6 |
7 | var isProduction = config.env === 'production';
8 | var configWebpack = config.webpack;
9 |
10 | // js方言
11 | const jsRules = {};
12 |
13 | var rules = [
14 | {
15 | test: /\.js$/,
16 | loader: 'happypack/loader?id=1',
17 | exclude: /node_modules/
18 | }
19 | ];
20 |
21 | if (!isProduction) {
22 | // 为了统计代码覆盖率,对 js 文件加入 istanbul-instrumenter-loader
23 | rules.push({
24 | test: /\.(js)$/,
25 | loader: 'istanbul-instrumenter-loader',
26 | exclude: /node_modules/,
27 | include: /src|packages/,
28 | enforce: 'post',
29 | options: {
30 | esModules: true
31 | }
32 | });
33 | }
34 |
35 | configWebpack.js.forEach((tpl) => {
36 | let rule = jsRules[tpl] || '';
37 | rule && rules.push(rule);
38 | });
39 |
40 | return rules;
41 | };
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 steamerjs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # steamer-react-component
2 |
3 | 用于开发 `react` 组件的脚手架
4 |
5 |
6 | ## 快速启动
7 |
8 | * 推荐 >> 使用[steamerjs](https://steamerjs.github.io/docs/projectkits/Bootstrap.html)安装
9 |
10 | ```javascript
11 |
12 | npm i -g steamerjs steamer-plugin-kit
13 |
14 | npm i -g steamer-react-component
15 |
16 | steamer kit
17 | ```
18 |
19 | * 或直接从github clone 下来
20 |
21 | ### 安装依赖
22 | ```bash
23 | npm i
24 | ```
25 |
26 | ### 开发
27 |
28 | ```bash
29 | npm run start 或 npm run dev
30 |
31 | // 打开链接,查看 demo
32 | localhost:9000
33 | ```
34 |
35 | > ps:此处在开启开发模式下默认会自动打开浏览器。
36 |
37 | ### 代码规范扫描
38 |
39 | ```bash
40 | npm run lint
41 | ```
42 |
43 | > ps:此处的扫描在开发模式下是默认开启的,不需要手动执行lint。
44 |
45 | ### 测试
46 |
47 | ```bash
48 | // 使用 jest 测试
49 | npm run test 或 npm run jest
50 |
51 | // 使用 karma 测试
52 | npm run karma
53 | ```
54 |
55 | ### 生产代码生成
56 |
57 | ```bash
58 | // 使用 babel 编译
59 | npm run dist 或 npm run babel
60 |
61 | // 使用 webapck 编译
62 | npm run webpack
63 | ```
64 |
65 | > ps:关于两种测试方式和两种编译方式的区别可查看[这里](https://steamerjs.github.io/docs/Componet-Standard.html#两种编译与两种测试方式)。
66 |
67 | ## 编写demo
68 |
69 | 直接在`/example/src/container/`目录下调整关于组件的使用逻辑和范例样式即可,如范例中该目录下的index.js和index.less文件。
70 |
71 |
72 | ## 脚手架文档
73 | [参见文档 - 组件脚手架](https://steamerjs.github.io/docs/componentkits/Starterkit.html)
74 |
75 | ## 文章参考
76 | * [从工程化角度讨论如何快速构建可靠React组件](https://github.com/lcxfs1991/blog/issues/18)
77 | * [聊一聊前端自动化测试](https://github.com/tmallfe/tmallfe.github.io/issues/37)
78 |
--------------------------------------------------------------------------------
/tools/script.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const utils = require('steamer-webpack-utils'),
4 | webpack = require('webpack'),
5 | opn = require('opn'),
6 | fs = require('fs'),
7 | path = require('path'),
8 | karmaServer = require('karma').Server;
9 |
10 | var isProduction = process.env.NODE_ENV === 'production',
11 | isKarma = process.env.KARMA_ENV === 'karma';
12 |
13 | const feature = require('./feature/feature');
14 |
15 | if (feature.installDependency()) {
16 | return;
17 | }
18 |
19 |
20 | if (isKarma) {
21 | let config = require('../config/project'),
22 | configWebpack = config.webpack;
23 |
24 | let server = new karmaServer({
25 | configFile: path.join(configWebpack.path.test, '/unit/karma.conf.js'),
26 | singleRun: true
27 | }, function() {
28 | console.log('karma test done!');
29 | opn(path.join(configWebpack.path.test, 'unit/coverage/lcov-report/index.html'));
30 | });
31 | server.start();
32 | }
33 | else if (!isProduction) {
34 | require('./server');
35 | }
36 | else if (isProduction) {
37 | compilerRun(require('./webpack.base'));
38 | }
39 |
40 | function compilerRun(config) {
41 | var compiler = webpack(config);
42 |
43 | compiler.run(function(err, stats) {
44 | if (!err) {
45 | // const jsonStats = stats.toJson();
46 | // print asset stats
47 | // fs.writeFileSync("stats.txt", JSON.stringify(jsonStats, " " , 4))
48 |
49 | console.log(stats.toString({
50 | assets: true,
51 | cached: true,
52 | colors: true,
53 | children: false,
54 | errors: true,
55 | warnings: true,
56 | version: true,
57 | }));
58 | }
59 | else {
60 | console.log(err);
61 | }
62 | });
63 | }
64 |
--------------------------------------------------------------------------------
/src/pindex.js:
--------------------------------------------------------------------------------
1 | /** @jsx h */
2 | import Preact, { h, Component } from 'preact';
3 | import Spin from './spin';
4 |
5 | import './index.less';
6 |
7 | export default class Spinner extends Component {
8 | constructor(props, context) {
9 | super(props, context);
10 | this.state = {
11 |
12 | };
13 | }
14 |
15 | componentWillMount() {
16 |
17 | }
18 |
19 | componentDidMount() {
20 | var opts = {
21 | lines: 12, // The number of lines to draw
22 | length: 3, // The length of each line
23 | width: 2, // The line thickness
24 | radius: 6, // The radius of the inner circle
25 | scale: 1.0, // Scales overall size of the spinner
26 | corners: 1, // Roundness (0..1)
27 | color: '#777', // #rgb or #rrggbb
28 | opacity: 1 / 4, // Opacity of the lines
29 | rotate: 0, // Rotation offset
30 | direction: 1, // 1: clockwise, -1: counterclockwise
31 | speed: 1, // Rounds per second
32 | trail: 100, // Afterglow percentage
33 | fps: 20, // Frames per second when using setTimeout()
34 | zIndex: 2e9, // Use a high z-index by default
35 | className: 'spin', // CSS class to assign to the element
36 | top: '50%', // center vertically
37 | left: '50%', // center horizontally
38 | shadow: false, // Whether to render a shadow
39 | hwaccel: false, // Whether to use hardware acceleration (might be buggy)
40 | position: 'absolute' // Element positioning
41 | };
42 | var target = document.getElementById('spin');
43 | var spinner = new Spin(opts).spin(target);
44 | }
45 |
46 | render() {
47 | console.log('render spinner');
48 |
49 | var isShow = this.props.isShow || false;
50 | var spinStyle = {
51 | display: (isShow) ? 'block' : 'none'
52 | };
53 |
54 | return (
55 |
56 | );
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tools/server.js:
--------------------------------------------------------------------------------
1 | const url = require('url');
2 | express = require('express'),
3 | app = express(),
4 | webpack = require('webpack'),
5 | webpackDevMiddleware = require("webpack-dev-middleware"),
6 | webpackHotMiddleware = require("webpack-hot-middleware"),
7 | proxy = require('http-proxy-middleware');
8 |
9 | var webpackConfig = require("./webpack.base.js"),
10 | config = require("../config/project"),
11 | configWebpack = config.webpack,
12 | port = configWebpack.port,
13 | route = Array.isArray(configWebpack.route) ? configWebpack.route : [configWebpack.route];
14 |
15 | function addProtocal(urlString) {
16 | if (!!~urlString.indexOf('http:') || !!~urlString.indexOf('https:')) {
17 | return urlString;
18 | }
19 | return 'http:' + urlString;
20 | }
21 |
22 | var urlObject = url.parse(addProtocal(configWebpack.webserver));
23 |
24 | for (var key in webpackConfig.entry) {
25 | webpackConfig.entry[key].unshift(`webpack-hot-middleware/client?reload=true&dynamicPublicPath=true&path=__webpack_hmr`);
26 | webpackConfig.entry[key].unshift('react-hot-loader/patch');
27 | }
28 |
29 | var compiler = webpack(webpackConfig);
30 | app.use(webpackDevMiddleware(compiler, {
31 | noInfo: true,
32 | stats: {
33 | colors: true
34 | },
35 | publicPath: configWebpack.webserver
36 | }));
37 |
38 | app.use(webpackHotMiddleware(compiler, {
39 | // 这里和上面的client配合,可以修正 webpack_hmr 的路径为项目路径的子路径,而不是直接成为 host 子路径(从publicPath开始,而不是根开始)
40 | // https://github.com/glenjamin/webpack-hot-middleware/issues/24
41 | path: `${urlObject.path}__webpack_hmr`
42 | }));
43 |
44 | // 静态资源转发
45 | route.forEach((rt) => {
46 | app.use(rt, proxy({target: `http://127.0.0.1:${port}`, pathRewrite: {[`^${rt}`] : '/'}}));
47 | });
48 |
49 | app.listen(port, function(err) {
50 | if (err) {
51 | console.error(err);
52 | }
53 | else {
54 | console.info("Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port);
55 | }
56 | });
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Spin from './spin';
3 |
4 | import './index.less';
5 |
6 | export default class Spinner extends Component {
7 | constructor(props, context) {
8 | super(props, context);
9 | this.state = {
10 |
11 | };
12 | }
13 |
14 | componentWillMount() {
15 |
16 | }
17 |
18 | componentDidMount() {
19 | let opts = {
20 | lines: 12, // The number of lines to draw
21 | length: 3, // The length of each line
22 | width: 2, // The line thickness
23 | radius: 6, // The radius of the inner circle
24 | scale: 1.0, // Scales overall size of the spinner
25 | corners: 1, // Roundness (0..1)
26 | color: '#777', // #rgb or #rrggbb
27 | opacity: 1 / 4, // Opacity of the lines
28 | rotate: 0, // Rotation offset
29 | direction: 1, // 1: clockwise, -1: counterclockwise
30 | speed: 1, // Rounds per second
31 | trail: 100, // Afterglow percentage
32 | fps: 20, // Frames per second when using setTimeout()
33 | zIndex: 2e9, // Use a high z-index by default
34 | className: 'spin', // CSS class to assign to the element
35 | top: '50%', // center vertically
36 | left: '50%', // center horizontally
37 | shadow: false, // Whether to render a shadow
38 | hwaccel: false, // Whether to use hardware acceleration (might be buggy)
39 | position: 'absolute' // Element positioning
40 | };
41 | let target = document.getElementById('spin');
42 | let spinner = new Spin(opts).spin(target);
43 | }
44 |
45 | render() {
46 | let isShow = this.props.isShow || false;
47 | let spinStyle = {
48 | display: (isShow) ? 'block' : 'none'
49 | };
50 |
51 | return (
52 |
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tools/plugins/resourcePlugins.js:
--------------------------------------------------------------------------------
1 | `use strict`;
2 |
3 | const path = require('path');
4 |
5 | let Clean = require('clean-webpack-plugin');
6 | let WriteFilePlugin = require('write-file-webpack-plugin');
7 | let HappyPack = require('happypack');
8 | let HtmlResWebpackPlugin = require('html-res-webpack-plugin');
9 | let MiniCssExtractPlugin = require('mini-css-extract-plugin');
10 |
11 | module.exports = function(config, webpack) {
12 |
13 | var configWebpack = config.webpack,
14 | isProduction = config.env === 'production';
15 |
16 | var plugins = [
17 | new HappyPack({
18 | id: '1',
19 | verbose: false,
20 | loaders: [{
21 | path: 'babel-loader',
22 | options: {
23 | cacheDirectory: './.cache/'
24 | }
25 | }]
26 | }),
27 | new MiniCssExtractPlugin({
28 | filename: `css/[name].css`,
29 | chunkFilename: 'css/[name].css'
30 | }),
31 |
32 | ];
33 |
34 | if (isProduction) {
35 |
36 | }
37 | else {
38 | if (configWebpack.showSource) {
39 | plugins.push(new WriteFilePlugin());
40 | }
41 |
42 | config.webpack.html.forEach(function(page, key) {
43 | plugins.push(new HtmlResWebpackPlugin({
44 | mode: "html",
45 | filename: page.key + ".html",
46 | template: page.path,
47 | htmlMinify: null,
48 | entryLog: true,
49 | removeUnMatchedAssets: true,
50 | env: isProduction ? 'production' : 'development',
51 | templateContent: function(tpl) {
52 | return tpl;
53 | }
54 | }));
55 | });
56 | }
57 |
58 | if (configWebpack.clean) {
59 | // 生产环境,只删除一次
60 | plugins.push(new Clean([isProduction ? configWebpack.path.dist : path.join(configWebpack.path.example, 'dev')], {root: path.resolve()}),);
61 | }
62 |
63 | return plugins;
64 | };
65 |
--------------------------------------------------------------------------------
/tools/rules/styleRules.js:
--------------------------------------------------------------------------------
1 | `use strict`;
2 |
3 | const path = require('path');
4 | const merge = require('lodash.merge');
5 |
6 | let MiniCssExtractPlugin = require('mini-css-extract-plugin');
7 |
8 | module.exports = function(config) {
9 |
10 | let configWebpack = config.webpack;
11 | let isProduction = config.env === 'production';
12 |
13 |
14 | let includePaths = [
15 | path.resolve('node_modules'),
16 | config.webpack.path.src
17 | ];
18 |
19 | // 样式loader
20 | let commonLoaders = [
21 | {
22 | loader: 'cache-loader',
23 | options: {
24 | // provide a cache directory where cache items should be stored
25 | cacheDirectory: path.resolve('.cache')
26 | }
27 | },
28 | {
29 | loader: 'css-loader',
30 | options: {
31 | localIdentName: '[name]-[local]-[hash:base64:5]',
32 | modules: config.webpack.cssModule,
33 | autoprefixer: true,
34 | minimize: true,
35 | importLoaders: 2
36 | }
37 | },
38 | {
39 | loader: 'postcss-loader'
40 | }
41 | ];
42 |
43 | if (isProduction) {
44 | commonLoaders.splice(0, 0, { loader: MiniCssExtractPlugin.loader });
45 | }
46 | else {
47 | commonLoaders.splice(0, 0, { loader: 'style-loader' });
48 | }
49 |
50 |
51 | const styleRules = {
52 | css: {
53 | test: /\.css$/,
54 | use: commonLoaders,
55 | },
56 | less: {
57 | test: /\.less$/,
58 | use: merge([], commonLoaders).concat([{
59 | loader: 'less-loader',
60 | options: {
61 | javascriptEnabled: true
62 | // paths: includePaths
63 | }
64 | }]),
65 | },
66 | stylus: {
67 | test: /\.styl$/,
68 | use: merge([], commonLoaders).concat([{
69 | loader: 'stylus-loader'
70 | }]),
71 | },
72 | sass: {
73 | test: /\.s(a|c)ss$/,
74 | use: merge([], commonLoaders).concat([{
75 | loader: 'sass-loader',
76 | options: {
77 | }
78 | }]),
79 | }
80 | };
81 |
82 | let rules = [];
83 |
84 | configWebpack.style.forEach((styleParam) => {
85 | let style = (styleParam === 'scss') ? 'sass' : styleParam;
86 | let rule = styleRules[style] || '';
87 | rule && rules.push(rule);
88 | });
89 |
90 | return rules;
91 | };
92 |
--------------------------------------------------------------------------------
/tools/webpack.base.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path'),
4 | fs = require('fs'),
5 | os = require('os'),
6 | utils = require('steamer-webpack-utils'),
7 | webpack = require('webpack'),
8 | webpackMerge = require('webpack-merge');
9 |
10 | var config = require('../config/project'),
11 | configWebpack = config.webpack,
12 | configWebpackMerge = config.webpackMerge,
13 | configCustom = config.custom,
14 | isProduction = config.env === 'production';
15 |
16 | var baseConfig = {
17 | mode: isProduction ? 'production' : 'development',
18 | context: configWebpack.path.src,
19 | entry: configWebpack.entry,
20 | output: {
21 | publicPath: isProduction ? `/${path.basename(configWebpack.path.dist)}/` : configWebpack.webserver,
22 | path: isProduction ? configWebpack.path.dist : path.join(configWebpack.path.example, 'dev'),
23 | filename: '[name].js',
24 | },
25 | module: {
26 | rules: []
27 | },
28 | resolve: {
29 | modules: [
30 | configWebpack.path.src,
31 | 'node_modules',
32 | path.join(configWebpack.path.src, 'css/sprites')
33 | ],
34 | extensions: [
35 | '.ts', '.tsx', '.js', '.jsx', '.css', '.scss', 'sass', '.less', '.styl',
36 | '.png', '.jpg', '.jpeg', '.ico', '.ejs', '.pug', '.handlebars', '.swf', '.vue'
37 | ],
38 | alias: {}
39 | },
40 | plugins: [],
41 | optimization: {},
42 | watch: !isProduction,
43 | performance: {
44 | hints: isProduction ? 'warning' : false,
45 | assetFilter: function(assetFilename) {
46 | return assetFilename.endsWith('.js') || assetFilename.endsWith('.css');
47 | }
48 | }
49 | };
50 |
51 | /************* 处理脚手架基础rules & plugins *************/
52 | var rules = fs.readdirSync(path.join(__dirname, 'rules')),
53 | plugins = fs.readdirSync(path.join(__dirname, 'plugins'));
54 |
55 | var baseConfigRules = [],
56 | baseConfigPlugins = [];
57 |
58 | rules.forEach((rule) => {
59 | baseConfigRules = baseConfigRules.concat(require(`./rules/${rule}`)(config));
60 | });
61 |
62 | plugins.forEach((plugin) => {
63 | baseConfigPlugins = baseConfigPlugins.concat(require(`./plugins/${plugin}`)(config, webpack));
64 | });
65 |
66 | baseConfig.module.rules = baseConfigRules;
67 | baseConfig.plugins = baseConfigPlugins;
68 | baseConfig.optimization = require('./optimization')(config, webpack);
69 |
70 | // console.log(rules, plugins);
71 |
72 | /************* base 与 user config 合并 *************/
73 | var userConfig = {
74 | output: configCustom.getOutput(),
75 | module: configCustom.getModule(),
76 | resolve: configCustom.getResolve(),
77 | externals: configCustom.getExternals(),
78 | plugins: configCustom.getPlugins()
79 | };
80 |
81 | var otherConfig = configCustom.getOtherOptions();
82 |
83 | for (let key in otherConfig) {
84 | userConfig[key] = otherConfig[key];
85 | }
86 |
87 | baseConfig = configWebpackMerge.mergeProcess(baseConfig);
88 |
89 | var webpackConfig = webpackMerge.smartStrategy(
90 | configWebpackMerge.smartStrategyOption
91 | )(baseConfig, userConfig);
92 |
93 | // console.log(JSON.stringify(webpackConfig, null, 4));
94 |
95 | module.exports = webpackConfig;
96 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "steamer-react-component",
3 | "version": "3.0.1",
4 | "description": "react component development starter kit",
5 | "main": "./.steamer/steamer-react-component.js",
6 | "scripts": {
7 | "start": "cross-env NODE_ENV=development node ./tools/script.js",
8 | "dev": "npm run start",
9 | "dist": "npm run babel",
10 | "test": "npm run karma",
11 | "babel": "babel src --out-dir dist --copy-files",
12 | "webpack": "cross-env NODE_ENV=production node ./tools/script.js",
13 | "jest": "jest --coverage --config ./jest.conf.json",
14 | "karma": "cross-env BABEL_ENV=test NODE_ENV=production KARMA_ENV=karma node ./tools/script.js",
15 | "lint": "eslint src/**/*.js && stylelint src/**/*.less",
16 | "lint:js": "eslint src/**/*.js",
17 | "lint:style": "stylelint src/**/*.less"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/steamerjs/steamer-react-component.git"
22 | },
23 | "keywords": [
24 | "steamer starterkit",
25 | "steamer",
26 | "react",
27 | "component",
28 | "starter kit",
29 | "boilerplate"
30 | ],
31 | "author": "steamerjs",
32 | "license": "MIT",
33 | "bugs": {
34 | "url": "https://github.com/steamerjs/steamer-react-component/issues"
35 | },
36 | "homepage": "https://github.com/steamerjs/steamer-react-component#readme",
37 | "dependencies": {},
38 | "devDependencies": {
39 | "@alloyteam/stylelint-config-standard": "0.0.2",
40 | "autoprefixer": "^9.1.5",
41 | "babel-cli": "6.26.0",
42 | "babel-core": "6.26.0",
43 | "babel-istanbul": "^0.12.2",
44 | "babel-jest": "^21.2.0",
45 | "babel-loader": "^7.1.2",
46 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
47 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
48 | "babel-preset-airbnb": "^2.4.0",
49 | "babel-preset-env": "^1.6.1",
50 | "babel-preset-es2015": "6.24.1",
51 | "babel-preset-react": "6.24.1",
52 | "cache-loader": "^1.2.2",
53 | "chai": "^4.2.0",
54 | "clean-webpack-plugin": "^0.1.19",
55 | "cross-env": "^5.2.0",
56 | "css-loader": "^1.0.0",
57 | "enzyme": "^3.7.0",
58 | "enzyme-adapter-react-15": "^1.1.1",
59 | "eslint": "^5.6.1",
60 | "eslint-config-alloy": "^1.4.2",
61 | "express": "^4.16.3",
62 | "file-loader": "^2.0.0",
63 | "happypack": "^5.0.0",
64 | "html-loader": "^0.5.5",
65 | "html-res-webpack-plugin": "^4.0.4",
66 | "http-proxy-middleware": "^0.19.0",
67 | "identity-obj-proxy": "^3.0.0",
68 | "istanbul-instrumenter-loader": "^3.0.1",
69 | "jest": "^23.6.0",
70 | "jest-environment-jsdom": "^23.4.0",
71 | "jsdom": "^12.2.0",
72 | "karma": "^3.0.0",
73 | "karma-chai": "^0.1.0",
74 | "karma-chrome-launcher": "^2.2.0",
75 | "karma-coverage-istanbul-reporter": "^2.0.4",
76 | "karma-mocha": "^1.3.0",
77 | "karma-phantomjs-launcher": "^1.0.4",
78 | "karma-sinon-chai": "^2.0.2",
79 | "karma-sourcemap-loader": "^0.3.7",
80 | "karma-spec-reporter": "0.0.32",
81 | "karma-webpack": "^3.0.5",
82 | "less": "^3.8.1",
83 | "less-loader": "^4.1.0",
84 | "lodash.merge": "^4.6.1",
85 | "mini-css-extract-plugin": "^0.4.4",
86 | "mocha": "^5.2.0",
87 | "opn": "^5.4.0",
88 | "optimize-css-assets-webpack-plugin": "^5.0.1",
89 | "postcss-import": "^12.0.0",
90 | "postcss-loader": "^3.0.0",
91 | "preact": "^8.3.1",
92 | "react": "^15.6.2",
93 | "react-addons-test-utils": "^15.6.2",
94 | "react-dom": "^15.6.2",
95 | "react-hot-loader": "^3.1.1",
96 | "react-test-renderer": "^15.6.2",
97 | "regenerator-runtime": "^0.12.1",
98 | "sinon": "^6.3.5",
99 | "sinon-chai": "^3.2.0",
100 | "steamer-webpack-utils": "^1.1.1",
101 | "style-loader": "^0.23.1",
102 | "stylelint": "^9.6.0",
103 | "uglifyjs-webpack-plugin": "^2.0.1",
104 | "url-loader": "^1.1.2",
105 | "webpack": "^4.20.2",
106 | "webpack-dev-middleware": "^3.4.0",
107 | "webpack-hot-middleware": "^2.24.3",
108 | "webpack-merge": "^4.1.4",
109 | "write-file-webpack-plugin": "^4.4.1"
110 | },
111 | "engines": {
112 | "node": ">=5.0.0"
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/tools/feature/feature.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // used for install dependencies and files to support certain kinds of features
4 |
5 | var path = require('path'),
6 | project = require('../../config/project'),
7 | pkgJson = require('../../package.json'),
8 | merge = require('lodash.merge'),
9 | spawnSync = require('child_process').spawnSync,
10 | utils = require('steamer-webpack-utils');
11 |
12 | var dependency = {
13 | template: {
14 | html: {
15 | 'html-loader': '^0.4.5'
16 | },
17 | handlebars: {
18 | 'handlebars-loader': '^1.5.0',
19 | 'handlebars': '^4.0.10'
20 | },
21 | pug: {
22 | 'pug-loader': '^2.3.0',
23 | 'pug': '^2.0.0-rc.2'
24 | },
25 | ejs: {
26 | 'ejs-compiled-loader': '^1.1.0',
27 | 'ejs': '^2.5.6'
28 | }
29 |
30 | },
31 | style: {
32 | css: {
33 | 'style-loader': '^0.18.2',
34 | 'css-loader': '^0.28.4'
35 | },
36 | less: {
37 | 'style-loader': '^0.18.2',
38 | 'css-loader': '^0.28.4',
39 | 'less': '^2.7.2',
40 | 'less-loader': '^4.0.4'
41 | },
42 | sass: {
43 | 'style-loader': '^0.18.2',
44 | 'css-loader': '^0.28.4',
45 | 'node-sass': '^4.5.3',
46 | 'sass-loader': '^6.0.6'
47 | },
48 | scss: {
49 | 'style-loader': '^0.18.2',
50 | 'css-loader': '^0.28.4',
51 | 'node-sass': '^4.5.3',
52 | 'sass-loader': '^6.0.6'
53 | },
54 | stylus: {
55 | 'style-loader': '^0.18.2',
56 | 'css-loader': '^0.28.4',
57 | 'stylus': '^0.54.5',
58 | 'stylus-loader': '^3.0.1'
59 | }
60 | },
61 | js: {
62 | ts: {
63 | 'awesome-typescript-loader': '^3.2.1',
64 | 'typescript': '^2.4.1',
65 | '@types/react': '^15.0.35',
66 | '@types/react-dom': '^15.5.1'
67 | }
68 | }
69 | };
70 |
71 | var files = {
72 | template: {},
73 | style: {},
74 | js: {
75 | ts: [
76 | {
77 | src: path.join(__dirname, './tsconfig.json'),
78 | dist: path.resolve('tsconfig.json')
79 | }
80 | ]
81 | }
82 | };
83 |
84 | module.exports = {
85 | installDependency: function() {
86 | console.log();
87 |
88 | var dependencies = merge({}, pkgJson.dependencies, pkgJson.devDependencies);
89 |
90 | var installDep = {},
91 | installFile = {
92 | template: {},
93 | style: {},
94 | js: {}
95 | },
96 | cmd = '';
97 |
98 | project.webpack.template.forEach((item1) => {
99 | let dep = dependency['template'][item1] || {};
100 |
101 | Object.keys(dep).forEach((item2) => {
102 | if (!dependencies[item2]) {
103 | installDep[item2] = dependency['template'][item1][item2];
104 | installFile.template[item1] = true;
105 | }
106 | });
107 | });
108 |
109 | project.webpack.style.forEach((item1) => {
110 | let dep = dependency['style'][item1] || {};
111 |
112 | Object.keys(dep).forEach((item2) => {
113 | if (!dependencies[item2]) {
114 | installDep[item2] = dependency['style'][item1][item2];
115 | installFile.style[item1] = true;
116 | }
117 | });
118 | });
119 |
120 | project.webpack.js.forEach((item1) => {
121 | let dep = dependency['js'][item1] || {};
122 |
123 | Object.keys(dep).forEach((item2) => {
124 | if (!dependencies[item2]) {
125 | installDep[item2] = dependency['js'][item1][item2];
126 | installFile.js[item1] = true;
127 | }
128 | });
129 | });
130 |
131 | Object.keys(installDep).forEach((item) => {
132 | cmd += (item + '@' + installDep[item] + ' ');
133 | });
134 |
135 | if (cmd) {
136 | utils.info('Start installing missing dependencies. Please wait......');
137 | this.copyFile(installFile);
138 | spawnSync(project.npm, ['install', '--save-dev', cmd], { stdio: 'inherit', shell: true });
139 | utils.info('Dependencies installation complete. Please run your command again.');
140 | return true;
141 | }
142 | else {
143 | return false;
144 | }
145 | },
146 | copyFile: function(installFile) {
147 | Object.keys(installFile.template).forEach((item1) => {
148 | let fileArr = files.template[item1] || [];
149 | fileArr.forEach((item2) => {
150 | utils.info('file ' + item2.src + ' is copyied to ' + item2.dist);
151 | utils.fs.copySync(item2.src, item2.dist);
152 | });
153 | });
154 |
155 | Object.keys(installFile.style).forEach((item1) => {
156 | let fileArr = files.style[item1] || [];
157 | fileArr.forEach((item2) => {
158 | utils.info('file ' + item2.src + ' is copyied to ' + item2.dist);
159 | utils.fs.copySync(item2.src, item2.dist);
160 | });
161 | });
162 | Object.keys(installFile.js).forEach((item1) => {
163 | let fileArr = files.js[item1] || [];
164 | fileArr.forEach((item2) => {
165 | utils.info('file ' + item2.src + ' is copyied to ' + item2.dist);
166 | utils.fs.copySync(item2.src, item2.dist);
167 | });
168 | });
169 | }
170 | };
--------------------------------------------------------------------------------
/config/project.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path'),
4 | os = require('os'),
5 | webpack = require('webpack'),
6 | utils = require('steamer-webpack-utils'),
7 | steamerConfig = require('./steamer.config'),
8 | __basename = path.dirname(__dirname),
9 | __env = process.env.NODE_ENV,
10 | isProduction = __env === 'production';
11 |
12 | var srcPath = path.resolve(__basename, "src"),
13 | devPath = path.resolve(__basename, "dev"),
14 | distPath = path.resolve(__basename, "dist"),
15 | examplePath = path.resolve(__basename, "example"),
16 | testPath = path.resolve(__basename, "test");
17 |
18 | var entry = {};
19 |
20 |
21 | if (isProduction) {
22 | entry = {
23 | index: path.join(srcPath, 'index.js'),
24 | pindex: path.join(srcPath, 'pindex.js')
25 | };
26 | }
27 | else {
28 | // 根据约定,自动扫描js entry,约定是example/src/page/xxx/main.js 或 example/src/page/xxx/main.jsx
29 | /**
30 | 获取结果示例
31 | {
32 | 'js/index': [path.join(configWebpack.path.src, "/page/index/main.js")],
33 | 'js/spa': [path.join(configWebpack.path.src, "/page/spa/main.js")],
34 | 'js/pindex': [path.join(configWebpack.path.src, "/page/pindex/main.jsx")],
35 | }
36 | */
37 | entry = utils.filterJsFileByCmd(utils.getJsEntry({
38 | srcPath: path.join(examplePath, "src/page"),
39 | fileName: "main",
40 | extensions: ["js", "jsx"],
41 | keyPrefix: "",
42 | level: 1
43 | }));
44 | }
45 |
46 | // ========================= webpack快捷配置 =========================
47 | // 基本情况下,你只需要关注这里的配置
48 | var config = {
49 | // ========================= webpack环境配置 =========================
50 | env: __env,
51 |
52 | // 默认使用的npm命令行
53 | npm: 'npm',
54 |
55 | webpack: {
56 |
57 | // ========================= webpack路径与url =========================
58 | // 项目路径
59 | path: {
60 | src: srcPath,
61 | dev: devPath,
62 | dist: distPath,
63 | example: examplePath,
64 | test: testPath,
65 | },
66 |
67 | // ========================= webpack服务器及路由配置 =========================
68 | // 开发服务器配置
69 | webserver: steamerConfig.webserver,
70 | port: steamerConfig.port, // port for local server
71 | route: [], // proxy route, 例如: /news/
72 |
73 | // ========================= webpack自定义配置 =========================
74 | // 是否显示开发环境下的生成文件
75 | showSource: true,
76 |
77 | // 是否清理生成文件夹
78 | clean: true,
79 |
80 | // 是否压缩
81 | compress: false,
82 |
83 | // javascript 方言, 目前仅支持 ts(typescript)
84 | js: [],
85 |
86 | // 预编译器,默认支持css 和 less. sass, scss 和 stylus 由npm-install-webpack-plugin自动安装
87 | style: [
88 | "css", "less"
89 | ],
90 | // 生产环境是否提取css
91 | extractCss: true,
92 | // 是否启用css模块化
93 | cssModule: false,
94 |
95 | // html 模板. 默认支持html 和 ejs, handlebars 和 pug 由npm-install-webpack-plugin自动安装
96 | template: [
97 | "html",
98 | ],
99 |
100 | // 利用DefinePlugin给应用注入变量
101 | injectVar: {
102 | "process.env": {
103 | NODE_ENV: JSON.stringify(__env)
104 | }
105 | },
106 |
107 | alias: {
108 |
109 | },
110 |
111 | // ========================= webpack entry配置 =========================
112 | entry: entry,
113 |
114 | // 自动扫描html,配合html-res-webpack-plugin
115 | /**
116 | 获取结果示例
117 | [
118 | {
119 | key: 'index',
120 | path: 'path/src/page/index/index.html'
121 | },
122 | {
123 | key: 'spa',
124 | path: 'path/src/page/spa/index.html'
125 | },
126 | {
127 | key: 'pindex',
128 | path: 'path/src/page/pindex/index.html'
129 | }
130 | ]
131 | */
132 | html: utils.filterHtmlFileByCmd(utils.getHtmlEntry({
133 | srcPath: path.join(examplePath, "src/page"),
134 | level: 1
135 | })),
136 |
137 | },
138 | };
139 |
140 |
141 |
142 | // ========================= webpack深度配置 =========================
143 | // 使用了webpack-merge与webpack.base.js进行配置合并
144 | // 如果上面的配置仍未能满足你,你可以在此处对webpack直接进行配置,这里的配置与webpack的配置项目一一对应
145 | config.custom = {
146 | // webpack output
147 | getOutput: function() {
148 |
149 | if (isProduction) {
150 | return {
151 | library: "lib",
152 | libraryTarget: "commonjs2",
153 | };
154 | }
155 | else {
156 | return {};
157 | }
158 | },
159 |
160 | // webpack module
161 | getModule: function() {
162 |
163 | var module = {
164 | rules: []
165 | };
166 |
167 | return module;
168 | },
169 |
170 | // webpack resolve
171 | getResolve: function() {
172 | return {
173 | alias: config.webpack.alias
174 | };
175 | },
176 |
177 | // webpack plugins
178 | getPlugins: function() {
179 | var plugins = [];
180 |
181 | return plugins;
182 | },
183 |
184 | // webpack externals
185 | getExternals: function() {
186 | if (isProduction) {
187 | return {
188 | 'react': "React",
189 | 'react-dom': "ReactDOM",
190 | 'preact': 'preact',
191 | };
192 | }
193 |
194 | return {};
195 | },
196 |
197 | // 其它 webpack 配置
198 | getOtherOptions: function() {
199 | return {};
200 | }
201 | };
202 |
203 | // ========================= webpack merge 的策略 =========================
204 | config.webpackMerge = {
205 | // webpack-merge smartStrategy 配置
206 | smartStrategyOption: {
207 | "module.rules": "append",
208 | "plugins": "append"
209 | },
210 |
211 | // 在smartStrategy merge 之前,用户可以先行对 webpack.base.js 的配置进行处理
212 | mergeProcess: function(webpackBaseConfig) {
213 |
214 | return webpackBaseConfig;
215 | }
216 | };
217 |
218 | module.exports = config;
219 |
--------------------------------------------------------------------------------
/test/unit/karma.conf.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | let webpackTestConf = require('../../tools/webpack.base.js');
4 |
5 | // karma 目前只认 umd,commonjs/commonjs2会报错
6 | webpackTestConf.output.libraryTarget = 'umd';
7 | delete webpackTestConf.output.library;
8 | // 需要删掉entry
9 | delete webpackTestConf.entry;
10 | // 不能设置 vue 的 externals
11 | delete webpackTestConf.externals;
12 | webpackTestConf.externals = {
13 | 'cheerio': 'window',
14 | 'react/addons': true,
15 | 'react/lib/ExecutionEnvironment': true,
16 | 'react/lib/ReactContext': true
17 | };
18 |
19 | module.exports = function(config) {
20 | config.set({
21 | // base path that will be used to resolve all patterns (eg. files, exclude)
22 | // basePath: '',
23 | // 手动引入 karma 的各项插件,如果不显式引入,karma 也会自动寻找 karma- 开头的插件并自动引入
24 | plugins: [
25 | 'karma-coverage-istanbul-reporter',
26 | 'karma-mocha',
27 | 'karma-sinon-chai',
28 | 'karma-webpack',
29 | 'karma-sourcemap-loader',
30 | 'karma-spec-reporter',
31 | 'karma-phantomjs-launcher',
32 | 'karma-chrome-launcher'
33 | ],
34 | // frameworks to use
35 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
36 | // 设定要使用的 frameworks
37 | frameworks: ['mocha', 'sinon-chai'],
38 | // list of files / patterns to load in the browser
39 | // 入口文件,按照 istanbul-instrumenter-loader 的要求来写
40 | files: ['./index.js'],
41 | // list of files to exclude
42 | exclude: [
43 | ],
44 | // preprocess matching files before serving them to the browser
45 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
46 | // 加入 webpack 与 sourcemap 插件
47 | preprocessors: {
48 | './index.js': ['webpack', 'sourcemap'],
49 | },
50 | // test results reporter to use
51 | // possible values: 'dots', 'progress'
52 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
53 | // 设定报告输出插件: spec 和 coverage-istanbul
54 | reporters: ['spec', 'coverage-istanbul'],
55 | // coverage-istanbul 输出配置,报告文件输出于根目录下的 coverage 文件夹内
56 | coverageIstanbulReporter: {
57 | // reports can be any that are listed here: https://github.com/istanbuljs/istanbul-reports/tree/590e6b0089f67b723a1fdf57bc7ccc080ff189d7/lib
58 | reports: ['html', 'lcovonly', 'text-summary'],
59 | // base output directory
60 | dir: path.join(__dirname, './coverage'),
61 | // if using webpack and pre-loaders, work around webpack breaking the source path
62 | fixWebpackSourcePaths: true,
63 | // Most reporters accept additional config options. You can pass these through the `report-config` option
64 | 'report-config': {
65 | // all options available at: https://github.com/istanbuljs/istanbul-reports/blob/590e6b0089f67b723a1fdf57bc7ccc080ff189d7/lib/html/index.js#L135-L137
66 | html: {
67 | // outputs the report in ./coverage/html
68 | subdir: 'html'
69 | }
70 | }
71 | },
72 | // web server port
73 | port: 9876,
74 | // enable / disable colors in the output (reporters and logs)
75 | colors: true,
76 | // level of logging
77 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
78 | logLevel: config.LOG_INFO,
79 | // enable / disable watching file and executing tests whenever any file changes
80 | autoWatch: true,
81 | // start these browsers
82 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
83 | browsers: ['PhantomJS', 'Chrome'],
84 | // Continuous Integration mode
85 | // if true, Karma captures browsers, runs the tests and exits
86 | singleRun: false,
87 | // Concurrency level
88 | // how many browser should be started simultaneous
89 | concurrency: Infinity,
90 | // 设定终端上不输出 webpack 的打包信息
91 | webpackMiddleware: {
92 | noInfo: true
93 | },
94 | // 用来预编译源代码的 webpack 配置,基本就是项目的 webpack 配置,但要去掉 entry 属性
95 | webpack: webpackTestConf,
96 |
97 | babelPreprocessor: {
98 | options: {
99 | presets: ['airbnb']
100 | }
101 | },
102 | });
103 | };
104 |
105 |
106 | // const path = require('path');
107 |
108 | // let webpackTestConf = require('../../tools/webpack.base.js');
109 |
110 | // // 给webpack基础配置增加一些针对测试环境的配置
111 | // webpackTestConf = Object.assign(webpackTestConf, {
112 | // // karma需要开启inline source map
113 | // devtool: "#inline-source-map",
114 | // // 针对enzyme的issue https://github.com/airbnb/enzyme/issues/503
115 | // externals: {
116 | // 'cheerio': 'window',
117 | // 'react/addons': true,
118 | // 'react/lib/ExecutionEnvironment': true,
119 | // 'react/lib/ReactContext': true
120 | // }
121 | // })
122 | // module.exports = function (config) {
123 | // config.set({
124 |
125 | // // 将被用于所有配置的基础路径 (eg. files, exclude)
126 | // basePath: '',
127 |
128 | // // 使用的测试框架 可用的框架: https://npmjs.org/browse/keyword/karma-adapter
129 | // frameworks: ['mocha'],
130 |
131 | // // Karma的入口文件
132 | // files: [
133 | // //{pattern: path.join(__basename, 'node_modules/chai/chai.js'),include: true},
134 | // './index.js'
135 | // ],
136 |
137 | // // 需排除的文件
138 | // exclude: [],
139 |
140 | // // 需要预处理的文件,比如需要webpack进行处理后再让karma运行服务器 可用的预处理器:
141 | // // https://npmjs.org/browse/keyword/karma-preprocessor
142 | // preprocessors: {
143 | // ['index.js']: ['webpack', 'sourcemap']
144 | // },
145 |
146 | // // 配置webpack
147 | // webpack: webpackTestConf,
148 | // webpackMiddleware: {
149 | // noInfo: true
150 | // },
151 |
152 | // // 测试结果报告,覆盖率报告如有需要在这里配置 可用的报告插件:
153 | // // https://npmjs.org/browse/keyword/karma-reporter
154 | // reporters: [
155 | // 'mocha', 'coverage'
156 | // ],
157 |
158 | // // mocha报告插件配置
159 | // mochaReporter: {
160 | // showDiff: true
161 | // },
162 |
163 | // // 覆盖率报告插件配置
164 | // coverageReporter: {
165 | // dir: path.join(__dirname, 'coverage'),
166 | // reporters: [
167 | // {
168 | // type: 'json',
169 | // subdir: '.',
170 | // file: 'coverage.json'
171 | // }, {
172 | // type: 'lcov',
173 | // subdir: '.'
174 | // }, {
175 | // type: 'text-summary'
176 | // }
177 | // ]
178 | // },
179 | // // 服务器端口
180 | // port: 9876,
181 |
182 | // // 是否要有颜色
183 | // colors: true,
184 |
185 | // // logging的级别 可用值: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN ||
186 | // // config.LOG_INFO || config.LOG_DEBUG
187 | // logLevel: config.LOG_INFO,
188 |
189 | // // 是否监听文件变动
190 | // autoWatch: true,
191 |
192 | // // 启动下列这些浏览器 可用的启动器: https://npmjs.org/browse/keyword/karma-launcher
193 | // browsers: [
194 | // 'Chrome', 'PhantomJS'
195 | // ],
196 |
197 | // // 持续集成模式 如果是true,Karma只会运行一次并退出
198 | // singleRun: true
199 | // })
200 | // }
201 |
--------------------------------------------------------------------------------
/src/spin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2011-2014 Felix Gnass
3 | * Licensed under the MIT license
4 | * http://spin.js.org/
5 | *
6 | * Example:
7 | var opts = {
8 | lines: 12 // The number of lines to draw
9 | , length: 7 // The length of each line
10 | , width: 5 // The line thickness
11 | , radius: 10 // The radius of the inner circle
12 | , scale: 1.0 // Scales overall size of the spinner
13 | , corners: 1 // Roundness (0..1)
14 | , color: '#000' // #rgb or #rrggbb
15 | , opacity: 1/4 // Opacity of the lines
16 | , rotate: 0 // Rotation offset
17 | , direction: 1 // 1: clockwise, -1: counterclockwise
18 | , speed: 1 // Rounds per second
19 | , trail: 100 // Afterglow percentage
20 | , fps: 20 // Frames per second when using setTimeout()
21 | , zIndex: 2e9 // Use a high z-index by default
22 | , className: 'spinner' // CSS class to assign to the element
23 | , top: '50%' // center vertically
24 | , left: '50%' // center horizontally
25 | , shadow: false // Whether to render a shadow
26 | , hwaccel: false // Whether to use hardware acceleration (might be buggy)
27 | , position: 'absolute' // Element positioning
28 | }
29 | var target = document.getElementById('foo')
30 | var spinner = new Spinner(opts).spin(target)
31 | */
32 | let prefixes = ['webkit', 'Moz', 'ms', 'O'],
33 | /* Vendor prefixes */
34 | animations = {},
35 | /* Animation rules keyed by their name */
36 | useCssAnimations, /* Whether to use CSS animations or setTimeout */
37 | sheet; /* A stylesheet to hold the @keyframe or VML rules. */
38 |
39 | /**
40 | * Utility function to create elements. If no tag name is given,
41 | * a DIV is created. Optionally properties can be passed.
42 | */
43 | function createEl(tag, prop) {
44 | let el = document.createElement(tag || 'div'),
45 | n;
46 |
47 | for (n in prop) el[n] = prop[n];
48 | return el;
49 | }
50 |
51 | /**
52 | * Appends children and returns the parent.
53 | */
54 | function ins(parent /* child1, child2, ... */) {
55 | for (let i = 1, n = arguments.length; i < n; i++) {
56 | parent.appendChild(arguments[i]);
57 | }
58 |
59 | return parent;
60 | }
61 |
62 | /**
63 | * Creates an opacity keyframe animation rule and returns its name.
64 | * Since most mobile Webkits have timing issues with animation-delay,
65 | * we create separate rules for each line/segment.
66 | */
67 | function addAnimation(alpha, trail, i, lines) {
68 | let name = ['opacity', trail, ~~(alpha * 100), i, lines].join('-'),
69 | start = 0.01 + i / lines * 100,
70 | z = Math.max(1 - (1 - alpha) / trail * (100 - start), alpha),
71 | prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase(),
72 | pre = prefix && '-' + prefix + '-' || '';
73 |
74 | if (!animations[name]) {
75 | sheet.insertRule(
76 | '@' + pre + 'keyframes ' + name + '{' +
77 | '0%{opacity:' + z + '}' +
78 | start + '%{opacity:' + alpha + '}' +
79 | (start + 0.01) + '%{opacity:1}' +
80 | (start + trail) % 100 + '%{opacity:' + alpha + '}' +
81 | '100%{opacity:' + z + '}' +
82 | '}', sheet.cssRules.length);
83 |
84 | animations[name] = 1;
85 | }
86 |
87 | return name;
88 | }
89 |
90 | /**
91 | * Tries various vendor prefixes and returns the first supported property.
92 | */
93 | function vendor(el, prop) {
94 | let s = el.style,
95 | pp,
96 | i;
97 |
98 | prop = prop.charAt(0).toUpperCase() + prop.slice(1);
99 | if (typeof s[prop] !== 'undefined') return prop;
100 | for (i = 0; i < prefixes.length; i++) {
101 | pp = prefixes[i] + prop;
102 | if (typeof s[pp] !== 'undefined') return pp;
103 | }
104 | }
105 |
106 | /**
107 | * Sets multiple style properties at once.
108 | */
109 | function css(el, prop) {
110 | for (let n in prop) {
111 | el.style[vendor(el, n) || n] = prop[n];
112 | }
113 |
114 | return el;
115 | }
116 |
117 | /**
118 | * Fills in default values.
119 | */
120 | function merge(obj) {
121 | for (let i = 1; i < arguments.length; i++) {
122 | let def = arguments[i];
123 |
124 | for (let n in def) {
125 | if (typeof obj[n] === 'undefined') obj[n] = def[n];
126 | }
127 | }
128 | return obj;
129 | }
130 |
131 | /**
132 | * Returns the line color from the given string or array.
133 | */
134 | function getColor(color, idx) {
135 | return typeof color === 'string' ? color : color[idx % color.length];
136 | }
137 |
138 | // Built-in defaults
139 |
140 | let defaults = {
141 | lines: 12, // The number of lines to draw
142 | length: 7, // The length of each line
143 | width: 5, // The line thickness
144 | radius: 10, // The radius of the inner circle
145 | scale: 1.0, // Scales overall size of the spinner
146 | corners: 1, // Roundness (0..1)
147 | color: '#000', // #rgb or #rrggbb
148 | opacity: 1 / 4, // Opacity of the lines
149 | rotate: 0, // Rotation offset
150 | direction: 1, // 1: clockwise, -1: counterclockwise
151 | speed: 1, // Rounds per second
152 | trail: 100, // Afterglow percentage
153 | fps: 20, // Frames per second when using setTimeout()
154 | zIndex: 2e9, // Use a high z-index by default
155 | className: 'spinner', // CSS class to assign to the element
156 | top: '50%', // center vertically
157 | left: '50%', // center horizontally
158 | shadow: false, // Whether to render a shadow
159 | hwaccel: false, // Whether to use hardware acceleration (might be buggy)
160 | position: 'absolute' // Element positioning
161 | };
162 |
163 | /** The constructor */
164 | function Spinner(o) {
165 | this.opts = merge(o || {}, Spinner.defaults, defaults);
166 | }
167 |
168 | // Global defaults that override the built-ins:
169 | Spinner.defaults = {};
170 |
171 | merge(Spinner.prototype, {
172 | /**
173 | * Adds the spinner to the given target element. If this instance is already
174 | * spinning, it is automatically removed from its previous target b calling
175 | * stop() internally.
176 | */
177 | spin: function(target) {
178 | this.stop();
179 |
180 | let self = this,
181 | o = self.opts,
182 | el = self.el = createEl(null, {
183 | className: o.className
184 | });
185 |
186 | css(el, {
187 | position: o.position,
188 | width: 0,
189 | zIndex: o.zIndex,
190 | left: o.left,
191 | top: o.top
192 | });
193 |
194 | if (target) {
195 | target.insertBefore(el, target.firstChild || null);
196 | }
197 |
198 | el.setAttribute('role', 'progressbar');
199 | self.lines(el, self.opts);
200 |
201 | if (!useCssAnimations) {
202 | // No CSS animation support, use setTimeout() instead
203 | let i = 0,
204 | start = (o.lines - 1) * (1 - o.direction) / 2,
205 | alpha,
206 | fps = o.fps,
207 | f = fps / o.speed,
208 | ostep = (1 - o.opacity) / (f * o.trail / 100),
209 | astep = f / o.lines;
210 | (function anim() {
211 | i++;
212 | for (let j = 0; j < o.lines; j++) {
213 | alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity);
214 |
215 | self.opacity(el, j * o.direction + start, alpha, o);
216 | }
217 | self.timeout = self.el && setTimeout(anim, ~~(1000 / fps));
218 | })();
219 | }
220 | return self;
221 | },
222 |
223 | /**
224 | * Stops and removes the Spinner.
225 | */
226 | stop: function() {
227 | let el = this.el;
228 |
229 | if (el) {
230 | clearTimeout(this.timeout);
231 | if (el.parentNode) el.parentNode.removeChild(el);
232 | this.el = null;
233 | }
234 | return this;
235 | },
236 |
237 | /**
238 | * Internal method that draws the individual lines. Will be overwritten
239 | * in VML fallback mode below.
240 | */
241 | lines: function(el, o) {
242 | let i = 0,
243 | start = (o.lines - 1) * (1 - o.direction) / 2,
244 | seg;
245 |
246 | function fill(color, shadow) {
247 | return css(createEl(), {
248 | position: 'absolute',
249 | width: o.scale * (o.length + o.width) + 'px',
250 | height: o.scale * o.width + 'px',
251 | background: color,
252 | boxShadow: shadow,
253 | transformOrigin: 'left',
254 | transform: 'rotate(' + ~~(360 / o.lines * i + o.rotate) + 'deg) translate(' +
255 | o.scale * o.radius + 'px' + ',0)',
256 | borderRadius: (o.corners * o.scale * o.width >> 1) + 'px'
257 | });
258 | }
259 |
260 | for (; i < o.lines; i++) {
261 | seg = css(createEl(), {
262 | position: 'absolute',
263 | top: 1 + ~(o.scale * o.width / 2) + 'px',
264 | transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
265 | opacity: o.opacity,
266 | animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i *
267 | o.direction, o.lines) + ' ' + 1 / o.speed + 's linear infinite'
268 | });
269 |
270 | if (o.shadow) {ins(seg, css(fill('#000', '0 0 4px #000'), {
271 | top: '2px'
272 | }));}
273 | ins(el, ins(seg, fill(getColor(o.color, i), '0 0 1px rgba(0,0,0,.1)')));
274 | }
275 | return el;
276 | },
277 |
278 | /**
279 | * Internal method that adjusts the opacity of a single line.
280 | * Will be overwritten in VML fallback mode below.
281 | */
282 | opacity: function(el, i, val) {
283 | if (i < el.childNodes.length) el.childNodes[i].style.opacity = val;
284 | }
285 |
286 | });
287 |
288 | function initVML() {
289 | /* Utility function to create a VML tag */
290 | function vml(tag, attr) {
291 | return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr);
292 | }
293 |
294 | // No CSS transforms but VML support, add a CSS rule for VML elements:
295 | sheet.addRule('.spin-vml', 'behavior:url(#default#VML)');
296 |
297 | Spinner.prototype.lines = function(el, o) {
298 | let r = o.scale * (o.length + o.width),
299 | s = o.scale * 2 * r;
300 |
301 | function grp() {
302 | return css(
303 | vml('group', {
304 | coordsize: s + ' ' + s,
305 | coordorigin: -r + ' ' + -r
306 | }), {
307 | width: s,
308 | height: s
309 | }
310 | );
311 | }
312 |
313 | let margin = -(o.width + o.length) * o.scale * 2 + 'px',
314 | g = css(grp(), {
315 | position: 'absolute',
316 | top: margin,
317 | left: margin
318 | }),
319 | i;
320 |
321 | function seg(i, dx, filter) {
322 | ins(
323 | g, ins(
324 | css(grp(), {
325 | rotation: 360 / o.lines * i + 'deg',
326 | left: ~~dx
327 | }), ins(
328 | css(
329 | vml('roundrect', {
330 | arcsize: o.corners
331 | }), {
332 | width: r,
333 | height: o.scale * o.width,
334 | left: o.scale * o.radius,
335 | top: -o.scale * o.width >> 1,
336 | filter: filter
337 | }
338 | ), vml('fill', {
339 | color: getColor(o.color, i),
340 | opacity: o.opacity
341 | }), vml('stroke', {
342 | opacity: 0
343 | }) // transparent stroke to fix color bleeding upon opacity change
344 | )
345 | )
346 | );
347 | }
348 |
349 | if (o.shadow) {
350 | for (i = 1; i <= o.lines; i++) {
351 | seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)');
352 | }
353 | }
354 |
355 | for (i = 1; i <= o.lines; i++) seg(i);
356 | return ins(el, g);
357 | };
358 |
359 | Spinner.prototype.opacity = function(el, i, val, o) {
360 | let c = el.firstChild;
361 |
362 | o = o.shadow && o.lines || 0;
363 | if (c && i + o < c.childNodes.length) {
364 | c = c.childNodes[i + o];
365 | c = c && c.firstChild;
366 | c = c && c.firstChild;
367 | if (c) c.opacity = val;
368 | }
369 | };
370 | }
371 |
372 | if (typeof document !== 'undefined') {
373 | sheet = (function() {
374 | let el = createEl('style', {
375 | type: 'text/css'
376 | });
377 |
378 | ins(document.getElementsByTagName('head')[0], el);
379 | return el.sheet || el.styleSheet;
380 | })();
381 |
382 | let probe = css(createEl('group'), {
383 | behavior: 'url(#default#VML)'
384 | });
385 |
386 | if (!vendor(probe, 'transform') && probe.adj) initVML();
387 | else useCssAnimations = vendor(probe, 'animation');
388 | }
389 |
390 | export default Spinner;
391 |
--------------------------------------------------------------------------------