├── .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 |
2 | Hello World
3 |
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 |
2 | Hello World
3 |
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 |
2 | Hello World
3 |
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 | }
--------------------------------------------------------------------------------