├── 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 |
13 |
14 | logo 15 |

16 | Edit src/App.js and save to reload. 17 |

18 | 24 | <% if (appStyle === 'Vue') { %> 25 | I love Vue! 26 | <% } else { %> 27 | Learn React 28 | <% } %> 29 | 30 |
31 |
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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------