├── .gitignore ├── .npmignore ├── .travis.yml ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── package.json ├── src └── index.ts ├── test ├── index.spec.js ├── test-crlf-parsed.ts ├── test-crlf.vue ├── test-lf-parsed.ts ├── test-lf.vue ├── test-no-above-parsed.ts ├── test-no-above.vue ├── test-no-script.vue ├── test-one-above-parsed.ts ├── test-one-above.vue ├── test-three-above-parsed.ts ├── test-three-above.vue ├── test-two-above-parsed.ts └── test-two-above.vue ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /lib/ 3 | node_modules/ 4 | 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | tsconfig.json 4 | tslint.json 5 | src/ 6 | test/ 7 | .travis.yml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: "8" 3 | deploy: 4 | provider: npm 5 | email: prograhammer@gmail.com 6 | skip_cleanup: true 7 | 'on': 8 | tags: true 9 | branch: master 10 | api_key: 11 | secure: CgB3e1pxeP7kLYHLt6ich7+LiFzVeOBtDuCveLD9rBycjLzPbNAlfqzDC2ce1MEJC2YOhsaytafeKoR9XynT50t09Ct/KtASeqAWTN+TjLbeAHilqHCj8iaYthOe7tMxxyVBEfvkxXTWcSyPH2qgCzHRC0k9F9RQM6wO7lLWP5Sz9TF524OQxCiljqrK1VrR4vKrsRMmiLolc2VWI0jQ+B+YFAjXYPsjooneztLViH95LGnv4BdaPOx3QZMBF6Hl4jK0/9zy6dNZUgAqRZVNXX530P7wX6adOGEkMieekKpn8ffsfPpaZKWFMrSf0toqF2UQyoqEkrcqBL82/Es2WNKYYKj+mhQkPTDWEYaBbO9jqNkwoBP8Gn+1mMjwmpI49n+0bDctdc6/tWDrRves1yRvqWWsJlf6ERibOlEx4qTgPyZSxUKsCC5J7z1iDa57MIY76C7kJi8RkulZU0NFLZsNL/s8t5yhq8dCTn0B1xLkqzQ5i9CEY0t3F2WNMJKT5xVGmPYqUANn1p4IR+SzSk9sVrsv6Qclbh3/kSQk+G8SO7hO/d9WLqqAMkguccJuFyTkA1anb5LKoGWleEq2/yKIdzZu7NQnm1Nywf9TOnkp6H687sUV3Z2OR2DWG8LA8QTHKtkXSAKtT36SnSz+cSq5yTwsrO2BvH1xRcB6b8M= 12 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 6 | "dbaeumer.vscode-eslint", 7 | "sysoev.language-stylus", 8 | "octref.vetur" 9 | ] 10 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to override the default and user settings 2 | { 3 | // Settings by file type. 4 | "[javascript]": { 5 | "editor.tabSize": 2, 6 | "editor.insertSpaces": true 7 | }, 8 | "[typescript]": { 9 | "editor.tabSize": 2, 10 | "editor.insertSpaces": true 11 | }, 12 | "[vue]": { 13 | "editor.tabSize": 2, 14 | "editor.insertSpaces": true 15 | }, 16 | "[stylus]": { 17 | "editor.tabSize": 2, 18 | "editor.insertSpaces": true 19 | }, 20 | 21 | // This is required for ESLint to work in Vue in VS Code. 22 | "eslint.options": { 23 | "extensions": [".html", ".js", ".vue", ".jsx"] 24 | }, 25 | "eslint.validate": [ 26 | { 27 | "language": "html", 28 | "autoFix": true 29 | }, 30 | { 31 | "language": "vue", 32 | "autoFix": true 33 | }, 34 | { 35 | "language": "javascript", 36 | "autoFix": true 37 | }, 38 | { 39 | "language": "javascriptreact", 40 | "autoFix": true 41 | } 42 | ], 43 | 44 | // When you hit ctrl+e to search, you don't want node_modules to be included. 45 | "search.exclude": { 46 | "**/.git": true, 47 | "**/node_modules": true, 48 | "**/tmp": true 49 | }, 50 | "emeraldwalk.runonsave": { 51 | "autoClearConsole": true, 52 | "commands": [ 53 | { 54 | "match": "\\.vue$", 55 | "cmd": "sed '1,/ 34 | 35 | 40 | ` 41 | 42 | const myScriptContents = vueParser.parse(vueFileContents, 'script', { lang: ['js', 'jsx'] }) 43 | 44 | console.log(myScriptContents) 45 | 46 | ``` 47 | 48 | The console output would like like this 49 | (notice default padding string is `// ` since comments are typically ignored by linters/compilers/etc.): 50 | 51 | ```text 52 | // 53 | // 54 | // 55 | // 56 | // 57 | // 58 | export default { 59 | name: 'Hello', 60 | 61 | data () { 62 | return { 63 | msg: 'Hello World!' 64 | } 65 | } 66 | 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-parser", 3 | "version": "1.1.6", 4 | "description": "Get contents from tags in .vue files (using AST tree).", 5 | "author": "David Graham ", 6 | "license": "MIT", 7 | "homepage": "https://github.com/prograhammer/vue-parser", 8 | "private": false, 9 | "main": "./lib/index.js", 10 | "typings": "./lib/index.d.ts", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/prograhammer/vue-parser.git" 14 | }, 15 | "keywords": [ 16 | "vue", 17 | "parser", 18 | "ast", 19 | "sfc" 20 | ], 21 | "bugs": { 22 | "url": "https://github.com/prograhammer/vue-parser/issues" 23 | }, 24 | "scripts": { 25 | "clean": "rimraf lib", 26 | "compile": "tsc ", 27 | "test": "npm run build && mocha -R spec ./test", 28 | "build": "npm run clean && npm run compile" 29 | }, 30 | "devDependencies": { 31 | "chai": "^4.1.2", 32 | "mocha": "^4.0.1", 33 | "rimraf": "^2.4.4", 34 | "tslint": "^5.0.0", 35 | "tslint-config-standard": "^7.0.0", 36 | "typescript": "^2.6.2" 37 | }, 38 | "engines": { 39 | "node": ">= 4.0.0", 40 | "npm": ">= 3.0.0" 41 | }, 42 | "dependencies": { 43 | "parse5": "^3.0.3" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as parse5 from 'parse5' 2 | 3 | /** 4 | * Type alias to work with Parse5's funky types. 5 | */ 6 | export type Node = (parse5.AST.Default.Element & parse5.AST.Default.Node) | undefined 7 | 8 | /** 9 | * Options for you. 10 | */ 11 | export interface Options { 12 | lang?: string | [string], 13 | emptyExport?: boolean // default: true 14 | } 15 | 16 | /** 17 | * Parse a vue file's contents. 18 | */ 19 | export function parse (input: string, tag: string, options?: Options): string { 20 | const emptyExport = options && options.emptyExport !== undefined ? options.emptyExport : true 21 | const node = getNode(input, tag, options) 22 | let parsed = padContent(node, input) 23 | 24 | // Add a default export of empty object if target tag script not found. 25 | // This fixes a TypeScript issue of "not a module". 26 | if (!parsed && tag === 'script' && emptyExport) { 27 | parsed = '// tslint:disable\nimport Vue from \'vue\'\nexport default Vue\n' 28 | } 29 | 30 | return parsed 31 | } 32 | 33 | /** 34 | * Pad the space above node with slashes (preserves content line/col positions in a file). 35 | */ 36 | function padContent (node: Node, input: string): string { 37 | if (!node || !node.__location) return '' 38 | 39 | const nodeContent = input.substring(node.__location.startTag.endOffset, node.__location.endTag.startOffset) 40 | const preNodeContent = input.substring(0, node.__location.startTag.endOffset) 41 | 42 | let nodeLines = (preNodeContent.match(new RegExp('\n', 'g')) || []).length + 1 43 | let remainingSlashes = preNodeContent.replace(/[\s\S]/gi, '/') 44 | let nodePrePad = '' 45 | let nodePad = '' 46 | 47 | // Reserve space for tslint:disable (if possible). 48 | if (nodeLines > 2) { 49 | nodePrePad = '//' + '\n' 50 | nodeLines-- 51 | remainingSlashes = remainingSlashes.substring(3) 52 | } 53 | 54 | // Pad with slashes (comments). 55 | for (let i = 1; i < nodeLines; i++) { 56 | nodePad += '//' + '\n' 57 | remainingSlashes = remainingSlashes.substring(3) 58 | } 59 | 60 | // Add tslint:disable and tslint:enable (if possible). 61 | if (nodePrePad && remainingSlashes.length > 50) { 62 | nodePrePad = '// tslint:disable' + '\n' 63 | remainingSlashes = remainingSlashes.substring('// tslint:disable\n// tslint:enable'.length) 64 | remainingSlashes = remainingSlashes.replace(/[\s\S]/gi, ' ') + ' // tslint:enable' 65 | } 66 | 67 | return nodePrePad + nodePad + remainingSlashes + nodeContent 68 | } 69 | 70 | /** 71 | * Get an array of all the nodes (tags). 72 | */ 73 | export function getNodes (input: string): parse5.AST.Default.Element[] { 74 | const rootNode = parse5.parseFragment(input, { locationInfo: true }) as parse5.AST.Default.DocumentFragment 75 | 76 | return rootNode.childNodes as parse5.AST.Default.Element[] 77 | } 78 | 79 | /** 80 | * Get the node. 81 | */ 82 | export function getNode (input: string, tag: string, options?: Options): Node { 83 | // Set defaults. 84 | const lang = options ? options.lang : undefined 85 | 86 | // Parse the Vue file nodes (tags) and find a match. 87 | return getNodes(input).find((node: parse5.AST.Default.Element) => { 88 | const tagFound = tag === node.nodeName 89 | const tagHasAttrs = ('attrs' in node) 90 | const langEmpty = lang === undefined 91 | let langMatch = false 92 | 93 | if (lang) { 94 | langMatch = tagHasAttrs && node.attrs.find((attr: parse5.AST.Default.Attribute) => { 95 | return attr.name === 'lang' && Array.isArray(lang) 96 | ? lang.indexOf(attr.value) !== -1 97 | : attr.value === lang 98 | }) !== undefined 99 | } 100 | 101 | return tagFound && (langEmpty || langMatch) 102 | }) as Node 103 | } 104 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const describe = require('mocha').describe 3 | const it = require('mocha').it 4 | const expect = require('chai').expect 5 | const fs = require('fs') 6 | const vueParser = require('../lib/index') 7 | 8 | describe('Tests', function () { 9 | 10 | function writeFiles () { 11 | let contents = '' 12 | let parsed = '' 13 | 14 | contents = contents = fs.readFileSync('./test/test-lf.vue', 'utf8') 15 | parsed = vueParser.parse(contents, 'script', { lang: 'ts' }) 16 | fs.writeFileSync('./test/test-lf-parsed.ts', parsed) 17 | 18 | contents = contents = fs.readFileSync('./test/test-crlf.vue', 'utf8') 19 | parsed = vueParser.parse(contents, 'script', { lang: 'ts' }) 20 | fs.writeFileSync('./test/test-crlf-parsed.ts', parsed) 21 | 22 | contents = contents = fs.readFileSync('./test/test-no-above.vue', 'utf8') 23 | parsed = vueParser.parse(contents, 'script', { lang: 'ts' }) 24 | fs.writeFileSync('./test/test-no-above-parsed.ts', parsed) 25 | 26 | contents = contents = fs.readFileSync('./test/test-one-above.vue', 'utf8') 27 | parsed = vueParser.parse(contents, 'script', { lang: 'ts' }) 28 | fs.writeFileSync('./test/test-one-above-parsed.ts', parsed) 29 | 30 | contents = contents = fs.readFileSync('./test/test-two-above.vue', 'utf8') 31 | parsed = vueParser.parse(contents, 'script', { lang: 'ts' }) 32 | fs.writeFileSync('./test/test-two-above-parsed.ts', parsed) 33 | 34 | contents = contents = fs.readFileSync('./test/test-three-above.vue', 'utf8') 35 | parsed = vueParser.parse(contents, 'script', { lang: 'ts' }) 36 | fs.writeFileSync('./test/test-three-above-parsed.ts', parsed) 37 | } 38 | 39 | writeFiles() 40 | 41 | it('should work with LF line terminators', function () { 42 | const contents = fs.readFileSync('./test/test-lf.vue', 'utf8') 43 | const parsed = fs.readFileSync('./test/test-lf-parsed.ts', 'utf8') 44 | 45 | expect(contents.indexOf(";")).to.be.equal(parsed.indexOf(";")) 46 | }) 47 | 48 | it('should work with CRLF line terminators', function () { 49 | const contents = fs.readFileSync('./test/test-crlf.vue', 'utf8') 50 | const parsed = fs.readFileSync('./test/test-crlf-parsed.ts', 'utf8') 51 | 52 | expect(contents.indexOf(";")).to.be.equal(parsed.indexOf(";")) 53 | }) 54 | 55 | it('should work with one line above target tag', function () { 56 | const contents = fs.readFileSync('./test/test-one-above.vue', 'utf8') 57 | const parsed = fs.readFileSync('./test/test-one-above-parsed.ts', 'utf8') 58 | 59 | expect(contents.indexOf(";")).to.be.equal(parsed.indexOf(";")) 60 | }) 61 | 62 | it('should work with two lines above target tag', function () { 63 | const contents = fs.readFileSync('./test/test-two-above.vue', 'utf8') 64 | const parsed = fs.readFileSync('./test/test-two-above-parsed.ts', 'utf8') 65 | 66 | expect(contents.indexOf(";")).to.be.equal(parsed.indexOf(";")) 67 | }) 68 | 69 | it('should work with three lines above target tag', function () { 70 | const contents = fs.readFileSync('./test/test-three-above.vue', 'utf8') 71 | const parsed = fs.readFileSync('./test/test-three-above-parsed.ts', 'utf8') 72 | 73 | expect(contents.indexOf(";")).to.be.equal(parsed.indexOf(";")) 74 | }) 75 | 76 | it('should work with no content above target tag', function () { 77 | const contents = fs.readFileSync('./test/test-no-above.vue', 'utf8') 78 | const parsed = fs.readFileSync('./test/test-no-above-parsed.ts', 'utf8') 79 | 80 | expect(contents.indexOf(";")).to.be.equal(parsed.indexOf(";")) 81 | }) 82 | 83 | it('should return empty string if no target tag found', function () { 84 | const contents = fs.readFileSync('./test/test-no-script.vue', 'utf8') 85 | const parsed = vueParser.parse(contents, 'script', { lang: 'ts', emptyExport: false }) 86 | 87 | expect(parsed).to.be.empty 88 | }) 89 | 90 | it('should return empty export if no target script found', function () { 91 | const contents = fs.readFileSync('./test/test-no-script.vue', 'utf8') 92 | const parsed = vueParser.parse(contents, 'script', { lang: 'ts' }) 93 | 94 | expect(parsed).to.be.equal('// tslint:disable\nimport Vue from \'vue\'\nexport default Vue\n') 95 | }) 96 | 97 | }) 98 | -------------------------------------------------------------------------------- /test/test-crlf-parsed.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable 2 | // 3 | // 4 | // 5 | // tslint:enable 6 | const foo = 'bar'; 7 | console.log(foo) 8 | -------------------------------------------------------------------------------- /test/test-crlf.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /test/test-lf-parsed.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable 2 | // 3 | // 4 | // 5 | // tslint:enable 6 | const foo = 'bar'; 7 | console.log(foo) 8 | -------------------------------------------------------------------------------- /test/test-lf.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /test/test-no-above-parsed.ts: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | const foo = 'bar'; 3 | console.log(foo) 4 | -------------------------------------------------------------------------------- /test/test-no-above.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | -------------------------------------------------------------------------------- /test/test-no-script.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /test/test-one-above-parsed.ts: -------------------------------------------------------------------------------- 1 | // 2 | //////////////// 3 | const foo = 'bar'; 4 | console.log(foo) 5 | -------------------------------------------------------------------------------- /test/test-one-above.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /test/test-three-above-parsed.ts: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // 4 | //////////// 5 | const foo = 'bar'; 6 | console.log(foo) 7 | -------------------------------------------------------------------------------- /test/test-three-above.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /test/test-two-above-parsed.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable 2 | // 3 | // tslint:enable 4 | const foo = 'bar'; 5 | console.log(foo) 6 | -------------------------------------------------------------------------------- /test/test-two-above.vue: -------------------------------------------------------------------------------- 1 | **************************************************************************************************************************************************************** 2 | 3 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // this aligns with Vue's browser support 4 | "target": "es5", 5 | // this enables stricter inference for data properties on `this` 6 | "strict": true, 7 | // if using webpack 2+ or rollup, to leverage tree shaking: 8 | "module": "umd", 9 | "moduleResolution": "node", 10 | "lib": [ 11 | "es6", "dom" 12 | ], 13 | "baseUrl": ".", 14 | "paths": { 15 | "@/*": [ 16 | "src/*" 17 | ] 18 | }, 19 | "declaration": true, 20 | "outDir": "lib" 21 | }, 22 | "include": [ 23 | "src/**/*.ts", 24 | "src/**/*.vue" 25 | //"test/**/*.ts", 26 | //"test/**/*.vue" 27 | ], 28 | "exclude": [ 29 | "node_modules" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint-config-standard" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "no-unused-variable": true, 9 | "quotemark": [true, "single"] 10 | }, 11 | "rulesDirectory": [] 12 | } --------------------------------------------------------------------------------