├── generator
├── template
│ ├── public
│ │ ├── favicon.ico
│ │ ├── manifest.json
│ │ └── index.html
│ └── src
│ │ ├── index.js
│ │ ├── index.css
│ │ ├── App.css
│ │ ├── App.js
│ │ └── logo.svg
└── index.js
├── prompts.js
├── package.json
├── index.js
├── utils
└── index.js
├── ui.js
└── README.md
/generator/template/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miccycn/vue-cli-plugin-react/HEAD/generator/template/public/favicon.ico
--------------------------------------------------------------------------------
/prompts.js:
--------------------------------------------------------------------------------
1 | module.exports = [{
2 | name: 'appStyle',
3 | type: 'list',
4 | message: '选择一个 Style 吧',
5 | choices: ['React', 'Vue'],
6 | default: 'React'
7 | }];
--------------------------------------------------------------------------------
/generator/template/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 | ReactDOM.render(, document.getElementById("root"));
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-cli-plugin-react",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC"
11 | }
12 |
--------------------------------------------------------------------------------
/generator/template/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/generator/template/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = (api, projectOptions) => {
2 | api.chainWebpack(webpackConfig => {
3 | webpackConfig
4 | .entry('app')
5 | .delete('./src/main.js')
6 | .add('./src/index.js')
7 | .end();
8 | });
9 |
10 | api.registerCommand(
11 | 'vueconf', {
12 | description: 'test',
13 | usage: 'vue-cli-service vueconf',
14 | options: {}
15 | },
16 | (args) => {
17 | console.log('测试输出projectOptions(包含pluginOptions)');
18 | console.log(projectOptions);
19 | }
20 | );
21 | };
--------------------------------------------------------------------------------
/utils/index.js:
--------------------------------------------------------------------------------
1 | function GetJson(func) {
2 | try {
3 | return eval(func);
4 | } catch (e) {
5 | return {};
6 | }
7 | }
8 |
9 | function MergePresets(babelConfigRaw = {}, preset = '') {
10 | const babelConfig = GetJson(babelConfigRaw);
11 | if (babelConfig.presets) {
12 | if (!babelConfig.presets.includes(preset)) {
13 | babelConfig.presets.push(preset);
14 | }
15 | } else {
16 | babelConfig.presets = [preset];
17 | }
18 | return `module.exports = ${JSON.stringify(babelConfig)}`;
19 | }
20 |
21 | module.exports = {
22 | MergePresets,
23 | GetJson
24 | };
--------------------------------------------------------------------------------
/generator/index.js:
--------------------------------------------------------------------------------
1 | const utils = require('../utils');
2 |
3 | module.exports = (api, option, rootOptions) => {
4 | // 扩展 package.json
5 | api.extendPackage({
6 | dependencies: {
7 | "react": "^16.0.0",
8 | "react-dom": "^16.0.0"
9 | },
10 | devDependencies: {
11 | "babel-preset-react-app": "^6.0.0"
12 | },
13 | eslintConfig: {
14 | "extends": [
15 | "plugin:react/recommended",
16 | ]
17 | },
18 | scripts: {
19 | "vueconf": "vue-cli-service vueconf"
20 | },
21 | });
22 | // 渲染模板
23 | api.render('./template', {
24 | ...option,
25 | });
26 |
27 | // 修改 babel.config.js
28 | api.postProcessFiles(files => {
29 | const babelConfigRaw = files['babel.config.js'];
30 | if (babelConfigRaw) {
31 | files['babel.config.js'] = utils.MergePresets(babelConfigRaw, 'react-app');
32 | }
33 | });
34 | };
--------------------------------------------------------------------------------
/generator/template/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 40vmin;
8 | }
9 |
10 | .App-header {
11 | <% if (appStyle === 'Vue') { %>
12 | background-color: #FFF;
13 | color: #2c3e50;
14 | <% } else { %>
15 | background-color: #282c34;
16 | color: white;
17 | <% } %>
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | }
25 |
26 | .App-link {
27 | <% if (appStyle === 'Vue') { %>
28 | color: #3eaf7c;
29 | <% } else { %>
30 | color: #61dafb;
31 | <% } %>
32 | }
33 |
34 | @keyframes App-logo-spin {
35 | from {
36 | transform: rotate(0deg);
37 | }
38 | to {
39 | transform: rotate(360deg);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/generator/template/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | React App
10 |
11 |
12 |
13 |
16 |
17 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/generator/template/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | <% if (appStyle === 'Vue') { %>
3 | import logo from './assets/logo.png';
4 | <% } else { %>
5 | import logo from './logo.svg';
6 | <% } %>
7 | import './App.css';
8 |
9 | class App extends Component {
10 | render() {
11 | return (
12 |
32 | );
33 | }
34 | }
35 |
36 | export default App;
37 |
--------------------------------------------------------------------------------
/ui.js:
--------------------------------------------------------------------------------
1 | module.exports = api => {
2 | api.describeConfig({
3 | id: 'vueconf.demo',
4 | name: 'vueconf',
5 | files: {
6 | vue: {
7 | js: ['vue.config.js']
8 | }
9 | },
10 | onRead: ({
11 | data
12 | }) => {
13 | return {
14 | prompts: [{
15 | name: 'themeColor',
16 | type: 'color',
17 | message: '选择一种配置的颜色',
18 | default: '#4DBA87',
19 | value: data.vue.pluginOptions && data.vue.pluginOptions.vueconf && data.vue.pluginOptions.vueconf.themeColor
20 | },
21 | {
22 | name: 'welcome',
23 | type: 'input',
24 | message: '项目介绍',
25 | description: '请输入这个项目的介绍',
26 | default: 'Learn React',
27 | value: data.vue.pluginOptions && data.vue.pluginOptions.vueconf && data.vue.pluginOptions.vueconf.welcome
28 | }
29 | ]
30 | }
31 | },
32 | onWrite: async ({
33 | api,
34 | prompts
35 | }) => {
36 | const result = {}
37 | for (const prompt of prompts) {
38 | result[`pluginOptions.vueconf.${prompt.id}`] = await api.getAnswer(prompt.id)
39 | }
40 | api.setData('vue', result)
41 | }
42 | })
43 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-cli-plugin-react
2 |
3 | A demo of lightning-talk in VueConf.HangZhou 2018.11
4 |
5 | ## 准备工作:
6 |
7 | 准备三个项目文件夹
8 |
9 | - myProject:用 vue-cli3 的默认选项生成的项目
10 |
11 | `vue create myProject`
12 |
13 | - reactProject:用 create-react-app 的默认选项生成的项目
14 |
15 | `create-react-app reactProject`
16 |
17 | - vue-cli-plugin-react:vue-cli 插件项目(我们后面主要是在这个文件夹里玩耍)
18 |
19 | `mkdir vue-cli-plugin-react`
20 |
21 | `cd vue-cli-plugin-react`
22 |
23 | `npm init`
24 |
25 | ### 步骤:
26 |
27 | 可通过`git checkout step1`,`git checkout step2`,……,`git checkout step10`切换步骤。
28 |
29 | 以下前 8 步都可以通过在 myProject 中执行:
30 |
31 | `yarn add --dev file:../vue-cli-plugin-react`
32 |
33 | `vue invoke react`
34 |
35 | `yarn serve`
36 |
37 | 观察报错和执行情况。
38 |
39 | 1. 初始化一个名叫 vue-cli-plugin-react 的空的 vue-cli 插件
40 | 2. 复制 create-react-app 中除配置文件以外的源文件到 template 目录
41 | 3. 在插件中扩展 package.json 安装 react 和 react-dom 等,渲染模板
42 | 4. 修改默认 webpack 入口
43 | 5. 修改 babel 配置文件
44 | 6. 增加 prompts
45 | 7. 修改 ejs 模板,使其接受 prompts 的参数
46 | 8. 增加自定义指令
47 | 此时可以在 myProject 中尝试运行
48 |
49 | `yarn vueconf`
50 |
51 | 控制台会输出当前项目相关的一些配置信息
52 |
53 | 9. 增加 ui.js
54 | 10. 完善 ui 选项的读写
55 |
56 | 此时可以在任意目录下尝试运行
57 |
58 | `vue ui`
59 |
60 | 在可视化界面中把 myProject 添加进项目管理界面
61 |
62 | 在左侧图标菜单栏中选择“配置”——“vueconf”
63 |
64 | 即可看到两个选项,这两个选项可被修改后保存到 `vue.config.js` 中,以便插件后续其他部分需要时调用。
65 |
66 | 还可在左侧图标菜单栏中选择“任务”——“vueconf”——“运行”,查看第 8 步时的输出。
67 |
--------------------------------------------------------------------------------
/generator/template/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------