├── index.js ├── .gitignore ├── .eslintrc ├── .vscode └── launch.json ├── src ├── bin │ └── react-native-vue-script.js ├── util │ ├── constants.js │ ├── watch │ │ ├── is.js │ │ ├── has-native-recursive.js │ │ └── index.js │ ├── parseCss.js │ ├── walk.js │ ├── addvm.js │ └── parseTransform.js └── scripts │ └── compiler.js ├── package.json └── README.md /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/bin/react-native-vue-script.js'); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | test/output 4 | docs/_book 5 | .DS_Store 6 | .idea 7 | *.iml 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended"], 3 | "env": { 4 | "es6": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "semi": 1, 9 | "no-extra-semi": 1, 10 | "no-console": 0, 11 | "no-undef-init": 1, 12 | "quotes": [2, "single"], 13 | "strict": [2, "never"] 14 | } 15 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 以学习相关的 Node.js 调试属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "启动程序", 11 | "program": "${file}" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/bin/react-native-vue-script.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const spawn = require('cross-spawn'); 4 | const script = process.argv[2]; 5 | const args = process.argv.slice(3); 6 | const validCommands = ['compiler']; 7 | 8 | if(validCommands.indexOf(script) !== -1) { 9 | const result = spawn.sync( 10 | 'node', 11 | ['--no-deprecation', require.resolve('../scripts/' + script + '.js')].concat(args), 12 | { stdio: 'inherit' } 13 | ); 14 | process.exit(result.status); 15 | } else { 16 | console.log( 17 | `Invalid command '${script}'. Please check if you need to update react-native-scripts.` 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/util/constants.js: -------------------------------------------------------------------------------- 1 | const HELPER_HEADER = '__react__vue__'; 2 | const SCRIPT_OPTIONS = `${HELPER_HEADER}options`; 3 | const TEMPLATE_RENDER = `${HELPER_HEADER}render`; 4 | const REACT_NATIVE = `${HELPER_HEADER}ReactNative`; 5 | const BUILD_COMPONENT = `${HELPER_HEADER}buildNativeComponent`; 6 | const COMPONENT_BUILDED = `${HELPER_HEADER}ComponentBuilded`; 7 | const VUE = `${HELPER_HEADER}Vue`; 8 | const REACT = `${HELPER_HEADER}React`; 9 | const COMPONENT = `${HELPER_HEADER}Component`; 10 | const PROP_TYPE = `${HELPER_HEADER}PropType`; 11 | const OBSERVER = `${HELPER_HEADER}observer`; 12 | const CSS = `${HELPER_HEADER}css`; 13 | 14 | module.exports = { 15 | HELPER_HEADER, 16 | SCRIPT_OPTIONS, 17 | TEMPLATE_RENDER, 18 | REACT_NATIVE, 19 | BUILD_COMPONENT, 20 | COMPONENT_BUILDED, 21 | VUE, 22 | REACT, 23 | COMPONENT, 24 | PROP_TYPE, 25 | OBSERVER, 26 | CSS 27 | }; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-vue-native-scripts", 3 | "version": "0.0.4", 4 | "description": "Compile Vue component to React Native", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "bin": { 8 | "react-vue-native-scripts": "./src/bin/react-native-vue-script.js" 9 | }, 10 | "author": "ZC", 11 | "license": "ISC", 12 | "dependencies": { 13 | "babel-core": "^6.25.0", 14 | "cross-spawn": "^5.1.0", 15 | "css-parse": "^2.0.0", 16 | "fs-extra": "^3.0.1", 17 | "js-beautify": "^1.6.14", 18 | "node-watch": "^0.5.4", 19 | "walk": "^2.3.9", 20 | "react-vue-template-compiler": "^0.0.7-alpha-1" 21 | }, 22 | "devDependencies": { 23 | "eslint": "^4.0.0" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/SmallComfort/react-vue/issues" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/SmallComfort/react-vue" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/util/watch/is.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | var is = { 5 | nil: function(item) { 6 | return (item === null) || (item === undefined); 7 | }, 8 | array: function(item) { 9 | return Array.isArray(item); 10 | }, 11 | buffer: function(item) { 12 | return Buffer.isBuffer(item); 13 | }, 14 | regExp: function(item) { 15 | return Object.prototype.toString.call(item) == '[object RegExp]'; 16 | }, 17 | string: function(item) { 18 | return typeof item === 'string'; 19 | }, 20 | func: function(item) { 21 | return typeof item === 'function'; 22 | }, 23 | exists: function(name) { 24 | return fs.existsSync(name); 25 | }, 26 | file: function(name) { 27 | return is.exists(name) 28 | ? fs.statSync(name).isFile() : false; 29 | }, 30 | sameFile: function(a, b) { 31 | return path.resolve(a) == path.resolve(b); 32 | }, 33 | directory: function(name) { 34 | return is.exists(name) 35 | ? fs.statSync(name).isDirectory() : false; 36 | }, 37 | symbolicLink: function(name) { 38 | return is.exists(name) 39 | ? fs.lstatSync(name).isSymbolicLink() : false; 40 | } 41 | }; 42 | 43 | module.exports = is; 44 | -------------------------------------------------------------------------------- /src/util/parseCss.js: -------------------------------------------------------------------------------- 1 | const parseTransform = require('./parseTransform'); 2 | 3 | const camelizeRE = /-(\w)/g; 4 | 5 | function camelize(str) { 6 | return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') 7 | } 8 | 9 | module.exports = function (ast) { 10 | const obj = {}; 11 | if (ast.type === 'stylesheet') { 12 | ast.stylesheet.rules.forEach(function (rule) { 13 | const declarationObj = {}; 14 | rule.declarations.forEach(function (declaration) { 15 | if (declaration.type === 'declaration') { 16 | let value = declaration.value; 17 | if (/px$/.test(value)) { 18 | value = parseFloat(value.replace(/px$/, '')); 19 | } else if (isNaN(value) === false){ 20 | value = parseFloat(value); 21 | } 22 | if (declaration.property === 'transform') { 23 | value = parseTransform(value); 24 | } 25 | declarationObj[camelize(declaration.property)] = value; 26 | } 27 | }); 28 | rule.selectors.forEach(function (selector) { 29 | if (selector.indexOf('.') === 0) { 30 | obj[selector.replace(/^\./, '')] = declarationObj; 31 | } 32 | }); 33 | }); 34 | } 35 | return obj; 36 | }; 37 | -------------------------------------------------------------------------------- /src/util/walk.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | function _walk (dir, options, cb) { 5 | if (fs.existsSync(dir)) { 6 | if (fs.statSync(dir).isDirectory()) { 7 | fs.readdir(dir, function(err, all) { 8 | if (err) { 9 | // don't throw permission errors. 10 | if (!/^(EPERM|EACCES)$/.test(err.code)) { 11 | throw err; 12 | } else { 13 | console.warn('Warning: Cannot access %s.', dir); 14 | } 15 | } else if (Array.isArray(all)) { 16 | all.forEach(function(f) { 17 | var sdir = path.join(dir, f); 18 | if (fs.existsSync(sdir) && f !== 'node_modules') { 19 | if (fs.statSync(sdir).isDirectory()) { 20 | _walk(sdir, options, cb); 21 | } else if (options.filter.test(sdir)){ 22 | cb(sdir); 23 | } 24 | } 25 | }); 26 | } 27 | }); 28 | } else if (options.filter.test(dir)){ 29 | cb(dir); 30 | } 31 | } 32 | } 33 | 34 | module.exports = function walk (dir, options, cb) { 35 | if (typeof options === 'function') { 36 | cb = options; 37 | options = { 38 | filter: /./ 39 | }; 40 | } 41 | return _walk(dir, options, cb); 42 | }; 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-vue-native-scripts 2 | 3 | Compile Vue component to React Native. 4 | 5 | ## Install 6 | ``` 7 | npm install --save react-vue react-vue-helper 8 | npm install --save-dev react-vue-native-scripts 9 | ``` 10 | 11 | ## Usage 12 | 13 | > Before you should get a [React Native Application](https://github.com/react-community/create-react-native-app) 14 | 15 | Add scripts commands in your `package.json`. 16 | 17 | ``` 18 | ... 19 | 20 | "scripts": { 21 | "compiler": "react-vue-native-scripts compiler", 22 | }, 23 | 24 | ... 25 | ``` 26 | 27 | #### cli 28 | ``` 29 | npm run compiler 30 | ``` 31 | 32 | ## Tips 33 | 34 | For react-native packager can not bundle `.vue` file, this scripts just compiled the file with `.vue` suffixed and generated a same name file with `.js` suffixed. 35 | 36 | In the react native application, you can simply `import` your Vue components as following 37 | 38 | ``` 39 | import VueComponent from './VueComponent' 40 | ``` 41 | 42 | There should be a file named `VueComponent.vue` in the corresponding folder, and the compiler would be generate a file named `VueComponent.js` in the same directory. 43 | 44 | In react-native packager, `import VueComponent from './VueComponent'` equal to `import VueComponent from './VueComponent.js'`. 45 | 46 | [demo](https://github.com/SmallComfort/HackerNews) 47 | -------------------------------------------------------------------------------- /src/util/addvm.js: -------------------------------------------------------------------------------- 1 | const babel = require('babel-core'); 2 | const constants = require('./constants'); 3 | 4 | const names = 'Infinity,undefined,NaN,isFinite,isNaN,console,' + 5 | 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + 6 | 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + 7 | 'require,' + // for webpack 8 | 'arguments'; // parsed as identifier but is a special keyword... 9 | 10 | const hash = Object.create(null); 11 | names.split(',').forEach(function (name) { 12 | hash[name] = true; 13 | }); 14 | 15 | function addvm (code) { 16 | const r = babel.transform(code, { 17 | plugins: [function ({ types: t }) { 18 | return { 19 | visitor: { 20 | Identifier: function (path) { 21 | if (path.parent.type === 'ObjectProperty' && path.parent.key === path.node) return; 22 | if (t.isDeclaration(path.parent.type) && path.parent.id === path.node) return; 23 | if (t.isFunction(path.parent.type) && path.parent.params.indexOf(path.node) > -1) return; 24 | if (path.parent.type === 'Property' && path.parent.key === path.node && !path.parent.computed) return; 25 | if (path.parent.type === 'MemberExpression' && path.parent.property === path.node && !path.parent.computed) return; 26 | if (path.parent.type === 'ArrayPattern') return; 27 | if (path.parent.type === 'ImportSpecifier') return; 28 | if (path.scope.hasBinding(path.node.name)) return; 29 | if (hash[path.node.name]) return; 30 | if (path.node.name.indexOf(constants.HELPER_HEADER) === 0) return; 31 | path.node.name = `vm['${path.node.name}']`; 32 | } 33 | } 34 | }; 35 | }] 36 | }); 37 | return r.code; 38 | } 39 | 40 | module.exports = addvm; 41 | -------------------------------------------------------------------------------- /src/util/watch/has-native-recursive.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var os = require('os'); 3 | var path = require('path'); 4 | var is = require('./is'); 5 | 6 | var IS_SUPPORT; 7 | var TEMP_DIR = os.tmpdir && os.tmpdir() 8 | || process.env.TMPDIR 9 | || process.env.TEMP 10 | || process.cwd(); 11 | 12 | function TempStack() { 13 | this.stack = []; 14 | } 15 | 16 | TempStack.prototype = { 17 | create: function(type, base) { 18 | var name = path.join(base, 19 | 'node-watch-' + Math.random().toString(16).substr(2) 20 | ); 21 | this.stack.push({ name: name, type: type }); 22 | return name; 23 | }, 24 | write: function(/* file */) { 25 | for (var i = 0; i < arguments.length; ++i) { 26 | fs.writeFileSync(arguments[i], ' '); 27 | } 28 | }, 29 | mkdir: function(/* dirs */) { 30 | for (var i = 0; i < arguments.length; ++i) { 31 | fs.mkdirSync(arguments[i]); 32 | } 33 | }, 34 | cleanup: function(fn) { 35 | try { 36 | var temp; 37 | while (temp = this.stack.pop()) { 38 | var type = temp.type; 39 | var name = temp.name; 40 | if (type == 'file' && is.file(name)) { 41 | fs.unlinkSync(name); 42 | } 43 | else if (type == 'dir' && is.directory(name)) { 44 | fs.rmdirSync(name); 45 | } 46 | } 47 | } 48 | finally { 49 | if (is.func(fn)) fn(); 50 | } 51 | } 52 | }; 53 | 54 | var pending = false; 55 | 56 | module.exports = function hasNativeRecursive(fn) { 57 | if (!is.func(fn)) { 58 | return false; 59 | } 60 | if (IS_SUPPORT !== undefined) { 61 | return fn(IS_SUPPORT); 62 | } 63 | 64 | if (!pending) { 65 | pending = true; 66 | } 67 | // check again later 68 | else { 69 | return setTimeout(function() { 70 | hasNativeRecursive(fn); 71 | }, 300); 72 | } 73 | 74 | var stack = new TempStack(); 75 | var parent = stack.create('dir', TEMP_DIR); 76 | var child = stack.create('dir', parent); 77 | var file = stack.create('file', child); 78 | 79 | stack.mkdir(parent, child); 80 | 81 | var options = { recursive: true }; 82 | var watcher = fs.watch(parent, options); 83 | var timer = setTimeout(function() { 84 | watcher.close(); 85 | stack.cleanup(function() { 86 | fn(IS_SUPPORT = false); 87 | }); 88 | }, 200); 89 | 90 | watcher.on('change', function(evt, name) { 91 | if (path.basename(file) == path.basename(name)) { 92 | watcher.close(); 93 | clearTimeout(timer); 94 | stack.cleanup(function() { 95 | fn(IS_SUPPORT = true); 96 | }); 97 | } 98 | }); 99 | stack.write(file); 100 | } 101 | -------------------------------------------------------------------------------- /src/util/parseTransform.js: -------------------------------------------------------------------------------- 1 | const TRANSFORM_TRANSLATE_REGEX = /translate\(([-+]?[\d]*\.?[\d]+)(px)?,[\s]+([-+]?[\d]*\.?[\d]+)(px)?\)/; 2 | const TRANSFORM_TRANSLATE_X_REGEX = /translateX\(([-+]?[\d]*\.?[\d]+)(px)?\)/; 3 | const TRANSFORM_TRANSLATE_Y_REGEX = /translateY\(([-+]?[\d]*\.?[\d]+)(px)?\)/; 4 | const TRANSFORM_ROTATE_REGEX = /rotate\(([-+]?[\d]*\.?[\d]+)deg\)/; 5 | const TRANSFORM_ROTATE_X_REGEX = /rotateX\(([-+]?[\d]*\.?[\d]+)deg\)/; 6 | const TRANSFORM_ROTATE_Y_REGEX = /rotateY\(([-+]?[\d]*\.?[\d]+)deg\)/; 7 | const TRANSFORM_ROTATE_Z_REGEX = /rotateZ\(([-+]?[\d]*\.?[\d]+)deg\)/; 8 | const TRANSFORM_SCALE_REGEX = /scale\(([-+]?[\d]*\.?[\d]+)\)/; 9 | const TRANSFORM_SCALE_X_REGEX = /scaleX\(([-+]?[\d]*\.?[\d]+)\)/; 10 | const TRANSFORM_SCALE_Y_REGEX = /scaleY\(([-+]?[\d]*\.?[\d]+)\)/; 11 | const TRANSFORM_SKEW_X_REGEX = /skewX\(([-+]?[\d]*\.?[\d]+)deg\)/; 12 | const TRANSFORM_SKEW_Y_REGEX = /skewY\(([-+]?[\d]*\.?[\d]+)deg\)/; 13 | 14 | module.exports = function (value) { 15 | const arr = []; 16 | if (TRANSFORM_ROTATE_REGEX.test(value)) { 17 | arr.push({ 18 | rotate: `${value.match(TRANSFORM_ROTATE_REGEX)[1]}deg` 19 | }); 20 | } 21 | if (TRANSFORM_ROTATE_X_REGEX.test(value)) { 22 | arr.push({ 23 | rotateX: `${value.match(TRANSFORM_ROTATE_X_REGEX)[1]}deg` 24 | }); 25 | } 26 | if (TRANSFORM_ROTATE_Y_REGEX.test(value)) { 27 | arr.push({ 28 | rotateY: `${value.match(TRANSFORM_ROTATE_Y_REGEX)[1]}deg` 29 | }); 30 | } 31 | if (TRANSFORM_ROTATE_Z_REGEX.test(value)) { 32 | arr.push({ 33 | rotateZ: `${value.match(TRANSFORM_ROTATE_Z_REGEX)[1]}deg` 34 | }); 35 | } 36 | if (TRANSFORM_SKEW_X_REGEX.test(value)) { 37 | arr.push({ 38 | skewX: `${value.match(TRANSFORM_SKEW_X_REGEX)[1]}deg` 39 | }); 40 | } 41 | if (TRANSFORM_SKEW_Y_REGEX.test(value)) { 42 | arr.push({ 43 | skewY: `${value.match(TRANSFORM_SKEW_Y_REGEX)[1]}deg` 44 | }); 45 | } 46 | if (TRANSFORM_SCALE_REGEX.test(value)) { 47 | let r = value.match(TRANSFORM_SCALE_REGEX)[1]; 48 | if (isNaN(r) === false) { 49 | r = parseFloat(r); 50 | } 51 | arr.push({ 52 | scale: r 53 | }); 54 | } 55 | if (TRANSFORM_SCALE_X_REGEX.test(value)) { 56 | let r = value.match(TRANSFORM_SCALE_X_REGEX)[1]; 57 | if (isNaN(r) === false) { 58 | r = parseFloat(r); 59 | } 60 | arr.push({ 61 | scaleX: r 62 | }); 63 | } 64 | if (TRANSFORM_SCALE_Y_REGEX.test(value)) { 65 | let r = value.match(TRANSFORM_SCALE_Y_REGEX)[1]; 66 | if (isNaN(r) === false) { 67 | r = parseFloat(r); 68 | } 69 | arr.push({ 70 | scaleY: r 71 | }); 72 | } 73 | if (TRANSFORM_TRANSLATE_REGEX.test(value)) { 74 | const rs = value.match(TRANSFORM_TRANSLATE_REGEX); 75 | let rx = rs[1]; 76 | let ry = rs[2]; 77 | if (isNaN(rx) === false) { 78 | rx = parseFloat(rx); 79 | } 80 | if (isNaN(ry) === false) { 81 | ry = parseFloat(ry); 82 | } 83 | arr.push({ 84 | translateX: rx 85 | }); 86 | arr.push({ 87 | translateY: ry 88 | }); 89 | } 90 | if (TRANSFORM_TRANSLATE_X_REGEX.test(value)) { 91 | let r = value.match(TRANSFORM_TRANSLATE_X_REGEX)[1]; 92 | if (isNaN(r) === false) { 93 | r = parseFloat(r); 94 | } 95 | arr.push({ 96 | translateX: r 97 | }); 98 | } 99 | if (TRANSFORM_TRANSLATE_Y_REGEX.test(value)) { 100 | let r = value.match(TRANSFORM_TRANSLATE_Y_REGEX)[1]; 101 | if (isNaN(r) === false) { 102 | r = parseFloat(r); 103 | } 104 | arr.push({ 105 | translateY: r 106 | }); 107 | } 108 | return arr; 109 | }; 110 | -------------------------------------------------------------------------------- /src/scripts/compiler.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const compiler = require('react-vue-template-compiler'); 3 | const cssParse = require('css-parse'); 4 | const beautify = require('js-beautify').js_beautify; 5 | const constants = require('../util/constants'); 6 | const addvm = require('../util/addvm'); 7 | const parseCss = require('../util/parseCss'); 8 | 9 | // the watch reference node-watch, there may be some changes in the future 10 | const watch = require('../util/watch'); 11 | 12 | const walk = require('../util/walk'); 13 | 14 | const FILTER = /\.vue$/; 15 | 16 | const DEFAULT_OUTPUT = { 17 | template: { 18 | import: `import { Component as ${constants.COMPONENT} } from 'react'`, 19 | render: `const ${constants.TEMPLATE_RENDER} = () => null` 20 | }, 21 | script: `const ${constants.SCRIPT_OPTIONS} = {}` 22 | }; 23 | 24 | walk('./', { 25 | filter: FILTER 26 | }, function (name) { 27 | action(name); 28 | }); 29 | 30 | watch('./', { 31 | recursive: true, 32 | filter: FILTER 33 | }, function (evt, name) { 34 | if (evt === 'update') { 35 | action(name); 36 | } else if (evt === 'remove') { 37 | remove(name); 38 | } 39 | }); 40 | 41 | function action (name) { 42 | fs.readFile(name, function (err, resource) { 43 | if (err) { 44 | throw err; 45 | } else { 46 | const code = resource.toString(); 47 | const cparsed = compiler.parseComponent(code, { pad: 'line' }); 48 | 49 | // console.log(cparsed); 50 | 51 | let output = ''; 52 | 53 | // add react-vue import 54 | output += `import ${constants.VUE}, { observer as ${constants.OBSERVER} } from 'react-vue'`; 55 | output += '\n'; 56 | 57 | // // add react import 58 | // output += `import ${constants.REACT} from 'react'` 59 | // output += '\n'; 60 | 61 | // add react-native import 62 | output += `import ${constants.REACT_NATIVE} from 'react-native'`; 63 | output += '\n'; 64 | 65 | // add prop-type import 66 | output += `import ${constants.PROP_TYPE} from 'prop-types'`; 67 | output += '\n'; 68 | 69 | // add component builder import 70 | output += `import { buildNativeComponent as ${constants.BUILD_COMPONENT} } from 'react-vue-helper'`; 71 | output += '\n'; 72 | 73 | // parse template 74 | const template = cparsed.template; 75 | let templateParsed = DEFAULT_OUTPUT.template; 76 | if (template) { 77 | const templateContent = template.content.replace(/\/\/\n/g, '').trim(); 78 | if (templateContent) { 79 | templateParsed = parseTemplate(templateContent); 80 | } 81 | } 82 | 83 | // add render dep import 84 | output += templateParsed.import; 85 | output += '\n'; 86 | 87 | // parse script 88 | const script = cparsed.script; 89 | let scriptParsed = DEFAULT_OUTPUT.script; 90 | if (script) { 91 | const scriptContent = script.content.replace(/\/\/\n/g, '').trim(); 92 | scriptParsed = parseScript(scriptContent); 93 | } 94 | 95 | // add vue options 96 | output += scriptParsed; 97 | output += '\n\n'; 98 | 99 | // add render funtion 100 | output += beautify(addvm(templateParsed.render), { 'indent_size': 2 }); 101 | output += '\n\n'; 102 | 103 | // parse css 104 | const styles = cparsed.styles; 105 | let cssParsed = {}; 106 | styles.forEach(function(v) { 107 | const cssAst = cssParse(v.content); 108 | cssParsed = Object.assign({}, cssParsed, parseCss(cssAst)); 109 | }); 110 | 111 | // add css obj 112 | output += `const ${constants.CSS} = ${JSON.stringify(cssParsed)}`; 113 | output += '\n\n'; 114 | 115 | // add builder 116 | output += `const ${constants.COMPONENT_BUILDED} = ${constants.BUILD_COMPONENT}(${constants.TEMPLATE_RENDER}, ${constants.SCRIPT_OPTIONS}, {Component: ${ 117 | constants.COMPONENT 118 | }, PropTypes: ${ 119 | constants.PROP_TYPE 120 | }, Vue: ${ 121 | constants.VUE 122 | }, ReactNative: ${ 123 | constants.REACT_NATIVE 124 | }, css: ${ 125 | constants.CSS 126 | }})`; 127 | output += '\n\n'; 128 | 129 | // export default 130 | output += `export default ${constants.OBSERVER}(${constants.COMPONENT_BUILDED})`; 131 | 132 | // beautiful 133 | // output = beautify(output, { 'indent_size': 2 }); 134 | 135 | fs.writeFile(name.replace(FILTER, '.js'), output, function (err) { 136 | if (err) { 137 | throw err; 138 | } 139 | }); 140 | } 141 | }); 142 | } 143 | 144 | function remove (name) { 145 | fs.unlink(name.replace(FILTER, '.js'), function (err) { 146 | if (err) { 147 | throw err; 148 | } 149 | }); 150 | } 151 | 152 | function parseTemplate (code) { 153 | const obj = compiler.nativeCompiler(code); 154 | return { 155 | import: obj.importCode, 156 | render: `const ${constants.TEMPLATE_RENDER} = ${obj.renderCode}` 157 | }; 158 | } 159 | 160 | function parseScript (code) { 161 | const s = `const ${constants.SCRIPT_OPTIONS} = `; 162 | code = code 163 | .replace(/[\s;]*module.exports[\s]*=/, `\n${s}`) 164 | .replace(/[\s;]*export[\s]+default[\s]*\{/, `\n${s} {`); 165 | return code; 166 | } 167 | -------------------------------------------------------------------------------- /src/util/watch/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var os = require('os'); 4 | var util = require('util'); 5 | var events = require('events'); 6 | 7 | var hasNativeRecursive = require('./has-native-recursive'); 8 | var is = require('./is'); 9 | 10 | var EVENT_UPDATE = 'update'; 11 | var EVENT_REMOVE = 'remove'; 12 | 13 | function makeArray(arr, offset) { 14 | return is.array(arr) 15 | ? arr : [].slice.call(arr, offset || 0); 16 | } 17 | 18 | function hasDup(arr) { 19 | return makeArray(arr).some(function(v, i, self) { 20 | return self.indexOf(v) !== i; 21 | }); 22 | } 23 | 24 | function unique(arr) { 25 | return makeArray(arr).filter(function(v, i, self) { 26 | return self.indexOf(v) === i; 27 | }); 28 | } 29 | 30 | function assign(obj/*, props */) { 31 | if (Object.assign) { 32 | return Object.assign.apply(Object, arguments); 33 | } 34 | return makeArray(arguments, 1) 35 | .reduce(function(mix, prop) { 36 | for (var name in prop) { 37 | if (prop.hasOwnProperty(name)) { 38 | mix[name] = prop[name]; 39 | } 40 | } 41 | return mix; 42 | }, obj); 43 | } 44 | 45 | function guard(fn) { 46 | return function(arg, action) { 47 | if (is.func(fn)) { 48 | if (fn(arg)) action(); 49 | } 50 | else if (is.regExp(fn)) { 51 | if (fn.test(arg)) action(); 52 | } 53 | else { 54 | action(); 55 | } 56 | } 57 | } 58 | 59 | function composeMessage(names) { 60 | return makeArray(names).map(function(n) { 61 | if (!is.exists(n)) return [EVENT_REMOVE, n]; 62 | else return [EVENT_UPDATE, n]; 63 | }); 64 | } 65 | 66 | function getMessages(cache) { 67 | var dup = hasDup(cache.map(function(c) { 68 | return c.replace(/^[~#]+|[~#]+$/, ''); 69 | })); 70 | 71 | // saving file from an editor maybe? 72 | if (dup) { 73 | var filtered = cache.filter(function(m) { 74 | return is.exists(m) 75 | }); 76 | return composeMessage(unique(filtered)); 77 | } 78 | else { 79 | return composeMessage(cache); 80 | } 81 | } 82 | 83 | function debounce(fn, delay) { 84 | var timer, cache = []; 85 | var info = fn.info; 86 | function handle() { 87 | getMessages(cache).forEach(function(msg) { 88 | if (info.options.encoding == 'buffer') { 89 | msg[1] = new Buffer(msg[1]); 90 | } 91 | fn.apply(null, msg); 92 | }); 93 | timer = null; 94 | cache = []; 95 | } 96 | return function(evt, name) { 97 | if (is.buffer(name)) { 98 | name = name.toString() 99 | } 100 | if (is.nil(name)) { 101 | name = ''; 102 | } 103 | cache.push( 104 | path.join(info.fpath, name) 105 | ); 106 | if (!timer) { 107 | timer = setTimeout(handle, delay || 200); 108 | } 109 | } 110 | } 111 | 112 | function getSubDirectories(dir, fn) { 113 | if (is.directory(dir)) { 114 | fs.readdir(dir, function(err, all) { 115 | if (err) { 116 | // don't throw permission errors. 117 | if (!/^(EPERM|EACCES)$/.test(err.code)) throw err; 118 | else console.warn('Warning: Cannot access %s.', dir); 119 | } 120 | else if (is.array(all)) { 121 | all.forEach(function(f) { 122 | var sdir = path.join(dir, f); 123 | if (is.directory(sdir)) fn(sdir); 124 | }); 125 | } 126 | }); 127 | } 128 | } 129 | 130 | var deprecationWarning = util.deprecate( 131 | function() {}, 132 | '(node-watch) First param in callback function\ 133 | is replaced with event name since 0.5.0, use\ 134 | `(evt, filename) => {}` if you want to get the filename' 135 | ); 136 | 137 | function Watcher() { 138 | events.EventEmitter.call(this); 139 | this.watchers = {}; 140 | } 141 | 142 | util.inherits(Watcher, events.EventEmitter); 143 | 144 | Watcher.prototype.expose = function() { 145 | var self = this; 146 | var methods = [ 147 | 'on', 'emit', 'close', 'isClosed', 'listeners', 'once', 148 | 'setMaxListeners', 'getMaxListeners' 149 | ]; 150 | return methods.reduce(function(expose, name) { 151 | expose[name] = function() { 152 | return self[name].apply(self, arguments); 153 | } 154 | return expose; 155 | }, {}); 156 | } 157 | 158 | Watcher.prototype.isClosed = function() { 159 | return !Object.keys(this.watchers).length 160 | } 161 | 162 | Watcher.prototype.close = function(fullPath) { 163 | var self = this; 164 | if (fullPath) { 165 | var watcher = this.watchers[fullPath]; 166 | if (watcher && watcher.close) { 167 | watcher.close(); 168 | delete self.watchers[fullPath]; 169 | } 170 | getSubDirectories(fullPath, function(fpath) { 171 | self.close(fpath); 172 | }); 173 | } else { 174 | var self = this; 175 | Object.keys(self.watchers).forEach(function(fpath) { 176 | var watcher = self.watchers[fpath]; 177 | if (watcher && watcher.close) { 178 | watcher.close(); 179 | } 180 | }); 181 | this.watchers = {}; 182 | } 183 | }; 184 | 185 | Watcher.prototype.add = function(watcher, info) { 186 | var self = this; 187 | info = info || {}; 188 | var fullPath = path.resolve(info.fpath); 189 | this.watchers[fullPath] = watcher; 190 | 191 | var callback = function(evt, name) { 192 | if (info.options.recursive) { 193 | hasNativeRecursive(function(has) { 194 | if (!has) { 195 | var fullPath = path.resolve(name); 196 | // remove watcher on removal 197 | if (evt == EVENT_REMOVE) { 198 | self.close(fullPath); 199 | } 200 | // watch new created directory 201 | else if (is.directory(name) && !self.watchers[fullPath]) { 202 | var filterGuard = guard(info.options.filter); 203 | filterGuard(name, function() { 204 | self.watchDirectory(name, info.options); 205 | }); 206 | } 207 | } 208 | }); 209 | } 210 | 211 | // watch single file 212 | if (info.compareName) { 213 | if (info.compareName(name)) { 214 | self.emit('change', evt, name); 215 | } 216 | } 217 | // watch directory 218 | else { 219 | var filterGuard = guard(info.options.filter); 220 | filterGuard(name, function() { 221 | if (self.flag) self.flag = ''; 222 | else self.emit('change', evt, name); 223 | }); 224 | } 225 | }; 226 | 227 | callback.info = info; 228 | 229 | watcher.on('error', function(err) { 230 | if (os.platform() == 'win32' && err.code == 'EPERM') { 231 | watcher.emit('change', EVENT_REMOVE, info.fpath && ''); 232 | self.flag = 'windows-error'; 233 | self.close(fullPath); 234 | } else { 235 | self.emit('error', err); 236 | } 237 | }); 238 | 239 | watcher.on('change', debounce(callback)); 240 | } 241 | 242 | Watcher.prototype.watchFile = function(file, options, fn) { 243 | var parent = path.join(file, '../'); 244 | var opts = assign({}, options, { 245 | recursive: false, 246 | filter: null 247 | }); 248 | 249 | var watcher = fs.watch(parent, opts); 250 | this.add(watcher, { 251 | type: 'file', 252 | fpath: parent, 253 | options: opts, 254 | compareName: function(n) { 255 | return is.sameFile(n, file); 256 | } 257 | }); 258 | 259 | if (is.func(fn)) { 260 | if (fn.length == 1) deprecationWarning(); 261 | this.on('change', fn); 262 | } 263 | } 264 | 265 | Watcher.prototype.watchDirectory = function(dir, options, fn) { 266 | var self = this; 267 | hasNativeRecursive(function(has) { 268 | options.recursive = !!options.recursive; 269 | var opts = assign({}, options); 270 | if (!has) { 271 | opts = assign(opts, { recursive: false }); 272 | } 273 | var watcher = fs.watch(dir, opts); 274 | 275 | self.add(watcher, { 276 | type: 'dir', 277 | fpath: dir, 278 | options: options 279 | }); 280 | 281 | if (is.func(fn)) { 282 | if (fn.length == 1) deprecationWarning(); 283 | self.on('change', fn); 284 | } 285 | 286 | if (options.recursive && !has) { 287 | getSubDirectories(dir, function(d) { 288 | var filterGuard = guard(options.filter); 289 | filterGuard(d, function() { 290 | self.watchDirectory(d, options); 291 | }); 292 | }); 293 | } 294 | }); 295 | } 296 | 297 | function composeWatcher(watchers) { 298 | var watcher = new Watcher(); 299 | watchers.forEach(function(w) { 300 | w.on('change', function(evt, name) { 301 | watcher.emit('change', evt, name); 302 | }); 303 | w.on('error', function(err) { 304 | watcher.emit('error', err); 305 | }); 306 | }); 307 | watcher.close = function() { 308 | watchers.forEach(function(w) { 309 | w.close(); 310 | }); 311 | } 312 | return watcher.expose(); 313 | } 314 | 315 | function watch(fpath, options, fn) { 316 | var watcher = new Watcher(); 317 | 318 | if (is.buffer(fpath)) { 319 | fpath = fpath.toString(); 320 | } 321 | 322 | if (is.array(fpath)) { 323 | return composeWatcher(unique(fpath).map(function(f) { 324 | return watch(f, options, fn); 325 | })); 326 | }; 327 | 328 | if (!is.exists(fpath)) { 329 | watcher.emit('error', 330 | new Error(fpath + ' does not exist.') 331 | ); 332 | } 333 | 334 | if (is.string(options)) { 335 | options = { 336 | encoding: options 337 | } 338 | } 339 | 340 | if (is.func(options)) { 341 | fn = options; 342 | options = {}; 343 | } 344 | 345 | if (arguments.length < 2) { 346 | options = {}; 347 | } 348 | 349 | if (is.file(fpath)) { 350 | watcher.watchFile(fpath, options, fn); 351 | } 352 | 353 | else if (is.directory(fpath)) { 354 | watcher.watchDirectory(fpath, options, fn); 355 | } 356 | 357 | return watcher.expose(); 358 | } 359 | 360 | module.exports = watch; 361 | --------------------------------------------------------------------------------