├── .gitignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── icon.png
├── package.json
├── src
├── Configuration.ts
├── FileOptionsParser.ts
├── StatusBarMessage.ts
├── StatusBarMessageTypes.ts
├── compiles
│ ├── less
│ │ ├── CompileLessCommand.ts
│ │ └── LessCompiler.ts
│ ├── sass
│ │ ├── CompileSassCommand.ts
│ │ └── SassCompiler.ts
│ └── typescript
│ │ ├── CompileTsCommand.ts
│ │ └── TsCompiler.ts
├── extension.ts
├── minify
│ ├── css
│ │ └── MinifyCssCommand.ts
│ └── js
│ │ └── MinifyJsCommand.ts
├── plugins
│ ├── pluginCleanCss.ts
│ ├── pluginGroup.ts
│ └── pluginSass2Less.ts
└── test
│ ├── runTest.ts
│ └── suite
│ ├── extension.test.ts
│ └── index.ts
├── tests
├── .vscode
│ └── settings.json
├── css
│ └── test.css
├── js
│ └── test.js
├── less
│ ├── helpers
│ │ ├── helpers.less
│ │ ├── mixins.less
│ │ ├── sub
│ │ │ └── variables.less
│ │ └── test.scss
│ ├── parent.less
│ └── test.less
├── scss
│ ├── helpers
│ │ ├── _helpers.scss
│ │ ├── _mixins.scss
│ │ └── sub
│ │ │ ├── _variables.scss
│ │ │ └── test2.scss
│ ├── parent.scss
│ └── test.scss
└── typescript
│ └── test.ts
├── tsconfig.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | node_modules
3 | .vscode-test/
4 | *.vsix
5 | package-lock.json
6 | **/.DS_Store
7 | tests/output/**
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | {
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "Launch Extension",
7 | "type": "extensionHost",
8 | "request": "launch",
9 | "runtimeExecutable": "${execPath}",
10 | "args": [
11 | "${workspaceFolder}/tests/",
12 | "--disable-extensions",
13 | "--extensionDevelopmentPath=${workspaceFolder}"
14 | ],
15 | "outFiles": [ "${workspaceRoot}/out/src/**/*.js" ],
16 | "preLaunchTask": "${defaultBuildTask}"
17 | },
18 | {
19 | "name": "Extension Tests",
20 | "type": "extensionHost",
21 | "request": "launch",
22 | "runtimeExecutable": "${execPath}",
23 | "args": [
24 | "${workspaceFolder}/tests/",
25 | "--disable-extensions",
26 | "--extensionDevelopmentPath=${workspaceFolder}",
27 | "--extensionTestsPath=${workspaceFolder}/out/src/test/suite/index"
28 | ],
29 | "outFiles": [ "${workspaceFolder}/out/src/test/**/*.js" ],
30 | "preLaunchTask": "${defaultBuildTask}"
31 | }
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "off",
11 | "easycompile.compile": {
12 | "typescript": false,
13 | "less": false,
14 | "sass": false
15 | }
16 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "watch",
9 | "problemMatcher": "$tsc-watch",
10 | "isBackground": true,
11 | "presentation": {
12 | "reveal": "never"
13 | },
14 | "group": {
15 | "kind": "build",
16 | "isDefault": true
17 | }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/src/test/**
4 | src/**
5 | tests/**
6 | node_modules/**
7 | .gitignore
8 | **/tsconfig.json
9 | **/*.map
10 | package-lock.json
11 | webpack.config.js
12 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | ### 1.2.4
3 | Fix issue [#42]
4 | ### 1.2.2
5 | Add inline lib option support for Typescript compile
6 |
7 | ### 1.2.1
8 | Fix multi main error for scss compile ([#30])
9 |
10 | ### 1.2.0
11 | Fix folder settings have no effect
12 | Optimize diagnostic message
13 |
14 | ### 1.1.9
15 | Change minify js "surround" default value ([#27])
16 | Fix typescript error message
17 |
18 | ### 1.1.8
19 | Fix sourceMap option ([#25])
20 | Implement Minify Css/JS settings ([#20] [#26])
21 | Optimize code
22 |
23 | ### 1.1.6
24 | Fix new version sass/scss import ([#24])
25 | Add channel message output
26 | Set setting scope to resource
27 |
28 | ### 1.1.5
29 | Fix crash bug
30 | Improve compile error message
31 |
32 | ### 1.1.4
33 | Upgrade dependencies
34 | Speed up extension
35 | Fix sass/scss inline source map ([#19])
36 |
37 | ### 1.1.2
38 | Upgrade Less complier to 3.8.1 ([#15])
39 |
40 | ### 1.1.1
41 | Add minify on save option ([#1])
42 | Integrate sass2less plugin. ([#12])
43 | Fix sass/scss compile bug ([#9])
44 |
45 | ### 1.1.0
46 | Fix compiler empty file throw error
47 | Fix sass/scss can not importing files from parent folder
48 |
49 | ### 1.0.9
50 | Fix sass/scss import files ([#2])
51 |
52 | ### 1.0.8
53 | Fix sass import and sourceMap
54 |
55 | ### 1.0.7
56 | Clean cache after sass/scss compile
57 |
58 | ### 1.0.6
59 | Add sass/scss compile support
60 |
61 | ### 1.0.5
62 | suport '*' in main option
63 |
64 | ### 1.0.4
65 | Add compress option for TSCompiler
66 |
67 | ### 1.0.3
68 | Include "Workspace Root/node_modules/@types" with typescript compiler
69 |
70 | ### 1.0.2
71 |
72 | Add surround option to minify js.
73 | * Example:
74 | ```json
75 | "easycompile.js": {
76 | "surround": "(function (define){ ${code} })(define)"
77 | }
78 | ```
79 |
80 | ### 1.0.1
81 |
82 | Add minify functions for JS and CSS
83 |
84 | ### 1.0.0
85 |
86 | Initial release
87 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 refgd
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # easy-compile README
2 |
3 | Easily work with LESS/SASS/SCSS/TYPESCRIPT files in Visual Studio Code.
4 |
5 | "Compile-on-save" for LESS/SASS/SCSS/TypeScript files without using a build task.
6 |
7 | ## Features
8 |
9 | * Compile TypeScript and Less/Sass/Scss on save
10 | * Support autoprefixer for Less/Sass/Scss
11 | * Support mearge all media queries
12 | * Support inline setting (Only for Complie)
13 | * minify .js and .css files
14 |
15 | ## Usage
16 |
17 | ### Complie
18 | For TypeScript, Only compile after you setup _outfile_ or _outdir_.
19 |
20 | ### Minify
21 | Run Command "Minify - Easy Complie" to minify files
22 |
23 | ## Extension Settings
24 |
25 | ### Settting
26 | easycompile.sass {}
27 |
28 | easycompile.less {}
29 |
30 | easycompile.typescript {}
31 |
32 | easycompile.css {
33 |
34 | `"outDir": { string }`
35 | * Redirect output to a different folder
36 | * support ${workspaceRoot}
37 |
38 | `"outExt": { string }`
39 | * allows you to specify an alternative output file extension
40 | * e.g. `.min.css` instead of `.css`
41 |
42 | `"autoprefixer": { string }`
43 | * this enables the [autoprefixer plugin](https://github.com/postcss/autoprefixer) (included)
44 | * e,g. `> 5%; last 2 Chrome versions; not ie 6-9`
45 |
46 | `"groupmedia": { boolean }`
47 | * This enables the [group media queries plugin](https://github.com/Se7enSky/group-css-media-queries) (included)
48 |
49 | `"sourceMap": { boolean }`
50 |
51 | `"sourceMapFileInline": { boolean }`
52 |
53 | }
54 |
55 | easycompile.js {
56 |
57 | `"outDir": { string }`
58 | * Redirect output to a different folder
59 | * support ${workspaceRoot}
60 |
61 | `"outExt": { string }`
62 | * allows you to specify an alternative output file extension
63 | * e.g. `.min.js` instead of `.js`
64 |
65 | `"surround": { string }`
66 | * put string surround the code
67 | * e.g. `(function (){ ${code} })()`
68 |
69 | `"compress": { object }`
70 | * implement UglifyJS Compress setting [[compress-options](https://github.com/mishoo/UglifyJS2#compress-options)]
71 |
72 | }
73 |
74 | ### Inline Setting (Only work for Less/Sass/Scss/Typescript)
75 | * Settings can also be specified per file as a comment on the _first_ line.
76 | * Settings are comma-separated and strings are _not_ "quoted".
77 | * Example:
78 |
79 | ```less
80 | // out: ../dist/app.css, compress: true, sourceMap: false, autoprefixer: last 5 versions, groupmedia: true
81 |
82 | body, html {
83 | ...
84 | }
85 | ```
86 |
87 | ```typescript
88 | // outdir: ../../
89 |
90 | import * ...
91 | ...
92 | ```
93 |
94 | ### Settings[Less/Scss/Sass]
95 | `main: { filepath: string | string[] }`
96 | * Compiles a different less file _instead_ of this one.
97 | * All other settings are ignored.
98 | * Filepath is relative to the current file.
99 | * Multiple main files can be specified (see [FAQ](#faq)).
100 |
101 | `out: { boolean | filepath: string | folderpath: string }`
102 | * Redirects the css output to a different file.
103 | * This setting can be used to override a project-wide `"out": false` setting, where you only want certain `.less` files to be generated.
104 | * If filepath is used, but no file extension is specified, it will append `.css`
105 | * If folderpath is used, the less filename will be used, but with the `.css` extension
106 | * Filepath is relative to the current file.
107 |
108 | `outExt: { string }`
109 | * The default output extension is `.css`.
110 | * This allows you to specify an alternative output file extension (e.g. `.wxss` instead of `.css`)
111 | * This applies to the `.map` file also (e.g. `.wxss.map`)
112 |
113 | `compress: { boolean }`
114 | * Compresses the css output by removing surplus white-space.
115 |
116 | `autoprefixer: { string | string[] }`
117 | * When present, this enables the [autoprefixer plugin](https://github.com/postcss/autoprefixer) (included).
118 | * This plugin automatically adds/removes vendor-prefixes needed to support a set of browsers which you specify.
119 | * The `autoprefixer` option _is_ the comma-separated list of `browsers` for autoprefixer to use (or alternatively a string array of them).
120 | * See [browserslist](https://github.com/ai/browserslist#queries) documentation for further examples of browser queries.
121 | * **NOTE**: If used with the inline setting, the browsers listed _must_ be unquoted and semi-colon separated (because comma is already the directive separator): e.g.
122 | `// autoprefixer: > 5%; last 2 Chrome versions; not ie 6-9, sourceMap: true, out: ../css/style.css`
123 |
124 | `groupmedia: { boolean }`
125 | * This enables the [group media queries plugin](https://github.com/Se7enSky/group-css-media-queries) (included).
126 |
127 |
128 | ### Tips
129 | * Ignore files
130 | ```json
131 | "easycompile.compile": {
132 | "ignore" : [
133 | "**/_*.scss"
134 | ]
135 | }
136 | ```
137 | * Enable minify on save
138 | ```json
139 | "easycompile.compile": {
140 | "minifyJsOnSave": true,
141 | "minifyCssOnSave": true
142 | }
143 | ```
144 |
145 |
146 | -----------------------------------------------------------------------------------------------------------
147 |
148 | ## Acknowledgements
149 | * Configuration concepts borrowed from [mrcrowl's](#https://github.com/mrcrowl) [vscode-easy-less](https://github.com/mrcrowl/vscode-easy-less).
150 |
151 | **Enjoy!**
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refgd/easy-complie/2e570732314f2ded042d7ee268a8702003e99c01/icon.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "easy-compile",
3 | "displayName": "Easy Compile",
4 | "description": "Easy to compile TypeScript/Less/Sass/Scss, Minify JS/CSS",
5 | "version": "1.2.6-beta",
6 | "publisher": "refgd",
7 | "engines": {
8 | "vscode": "^1.34.0"
9 | },
10 | "license": "MIT",
11 | "categories": [
12 | "Other"
13 | ],
14 | "icon": "icon.png",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/refgd/easy-complie.git"
18 | },
19 | "activationEvents": [
20 | "onLanguage:less",
21 | "onLanguage:typescript",
22 | "onLanguage:scss",
23 | "onLanguage:sass",
24 | "onLanguage:css",
25 | "onLanguage:javascript",
26 | "onCommand:easyCompile.compile",
27 | "onCommand:easyCompile.minifydir",
28 | "onCommand:easyCompile.minify"
29 | ],
30 | "main": "./out/src/extension",
31 | "contributes": {
32 | "commands": [
33 | {
34 | "command": "easyCompile.compile",
35 | "title": "Compile - Easy Compile"
36 | },
37 | {
38 | "command": "easyCompile.minify",
39 | "title": "Minify - Easy Compile"
40 | },
41 | {
42 | "command": "easyCompile.minifydir",
43 | "title": "Minify Directory - Easy Compile"
44 | }
45 | ],
46 | "configuration": {
47 | "type": "object",
48 | "title": "Easy Compile configuration",
49 | "scope": "resource",
50 | "properties": {
51 | "easycompile.compile": {
52 | "type": "object",
53 | "description": "Configuration options for easycompile.",
54 | "scope": "resource",
55 | "properties": {
56 | "less": {
57 | "type": "boolean",
58 | "description": "Compile Less?",
59 | "default": true
60 | },
61 | "sass": {
62 | "type": "boolean",
63 | "description": "Compile Sass/Scss?",
64 | "default": true
65 | },
66 | "typescript": {
67 | "type": "boolean",
68 | "description": "Compile Typescript?",
69 | "default": true
70 | },
71 | "minifyJsOnSave": {
72 | "type": "boolean",
73 | "description": "Minify JS file on save?",
74 | "default": false
75 | },
76 | "minifyCssOnSave": {
77 | "type": "boolean",
78 | "description": "Minify CSS file on save?",
79 | "default": false
80 | },
81 | "ignore": {
82 | "type": "array",
83 | "description": "Minify CSS file on save?",
84 | "default": []
85 | }
86 | }
87 | },
88 | "easycompile.js": {
89 | "type": "object",
90 | "description": "Configuration options for minify Javascript.",
91 | "scope": "resource",
92 | "properties": {
93 | "surround": {
94 | "type": "string",
95 | "description": "",
96 | "default": ""
97 | }
98 | }
99 | },
100 | "easycompile.css": {
101 | "type": "object",
102 | "description": "Configuration options for Css.",
103 | "scope": "resource",
104 | "properties": {}
105 | },
106 | "easycompile.typescript": {
107 | "type": "object",
108 | "description": "Configuration options for TypeScript.",
109 | "scope": "resource",
110 | "properties": {
111 | "surround": {
112 | "type": "string",
113 | "description": "",
114 | "default": "(function (define){ ${code} })(define)"
115 | }
116 | }
117 | },
118 | "easycompile.sass": {
119 | "type": "object",
120 | "description": "Configuration options for SASS/SCSS.",
121 | "scope": "resource",
122 | "properties": {
123 | "compress": {
124 | "type": "boolean",
125 | "description": "Compress .css files? (removes unnecessary white-space)",
126 | "default": false
127 | },
128 | "sourceMap": {
129 | "type": "boolean",
130 | "description": "Should .map files be generated?",
131 | "default": false
132 | },
133 | "sourceMapFileInline": {
134 | "type": "boolean",
135 | "description": "Should source maps be inlined within the .css file? (requires sourceMap: true)",
136 | "default": false
137 | },
138 | "out": {
139 | "type": [
140 | "boolean",
141 | "string",
142 | "null"
143 | ],
144 | "description": "Default 'out' setting. Set to false to default to no output.",
145 | "default": null
146 | },
147 | "main": {
148 | "type": [
149 | "string",
150 | "array"
151 | ],
152 | "description": "Compile specific .sass/.scss file(s) when any .sass/.scss file is saved.",
153 | "default": "main.scss"
154 | },
155 | "autoprefixer": {
156 | "type": [
157 | "string",
158 | "array",
159 | "null"
160 | ],
161 | "description": "The 'browsers' argument for autoprefixer plugin (see https://github.com/ai/browserslist#queries)",
162 | "default": "last 5 versions"
163 | },
164 | "groupmedia": {
165 | "type": "boolean",
166 | "description": "Mearge all media queries",
167 | "default": true
168 | }
169 | }
170 | },
171 | "easycompile.less": {
172 | "type": "object",
173 | "description": "Configuration options for LESS.",
174 | "scope": "resource",
175 | "properties": {
176 | "compress": {
177 | "type": "boolean",
178 | "description": "Compress .css files? (removes unnecessary white-space)",
179 | "default": false
180 | },
181 | "ieCompat": {
182 | "type": "boolean",
183 | "description": "IE8 compatiblity mode? (restricts size of data-uri to 32KB)",
184 | "default": true
185 | },
186 | "sourceMap": {
187 | "type": "boolean",
188 | "description": "Should .map files be generated?",
189 | "default": false
190 | },
191 | "sourceMapFileInline": {
192 | "type": "boolean",
193 | "description": "Should source maps be inlined within the .css file? (requires sourceMap: true)",
194 | "default": false
195 | },
196 | "out": {
197 | "type": [
198 | "boolean",
199 | "string",
200 | "null"
201 | ],
202 | "description": "Default 'out' setting. Set to false to default to no output.",
203 | "default": null
204 | },
205 | "outExt": {
206 | "type": "string",
207 | "description": "The file extension to use for generated .css files",
208 | "default": ".css"
209 | },
210 | "main": {
211 | "type": [
212 | "string",
213 | "array"
214 | ],
215 | "description": "Compile specific .less file(s) when any .less file is saved.",
216 | "default": "main.less"
217 | },
218 | "relativeUrls": {
219 | "type": "boolean",
220 | "description": "Rewrite URLs from imported files, relative to the importing file (default: false)",
221 | "default": false
222 | },
223 | "autoprefixer": {
224 | "type": [
225 | "string",
226 | "array",
227 | "null"
228 | ],
229 | "description": "The 'browsers' argument for autoprefixer plugin (see https://github.com/ai/browserslist#queries)",
230 | "default": "last 5 versions"
231 | },
232 | "groupmedia": {
233 | "type": "boolean",
234 | "description": "Mearge all media queries",
235 | "default": true
236 | },
237 | "sass2less": {
238 | "type": "boolean",
239 | "description": "Convert SASS files to LESS",
240 | "default": true
241 | }
242 | }
243 | }
244 | }
245 | }
246 | },
247 | "scripts": {
248 | "vscode:prepublish": "npm run compile",
249 | "webpack": "SET NODE_OPTIONS=--openssl-legacy-provider && webpack --mode production",
250 | "webpack-dev": "webpack --mode development --openssl-legacy-provider",
251 | "compile": "tsc -p ./",
252 | "watch": "tsc -watch -p ./",
253 | "pretest": "npm run compile",
254 | "test": "node ./out/src/test/runTest.js"
255 | },
256 | "devDependencies": {
257 | "@types/mocha": "^5.2.7",
258 | "@types/node": "^12.12.22",
259 | "@types/vscode": "^1.34.0",
260 | "fs-plus": "^3.1.1",
261 | "mocha": "^6.2.2",
262 | "ts-loader": "^6.2.1",
263 | "vscode-test": "^1.3.0",
264 | "webpack": "^4.41.4",
265 | "webpack-cli": "^3.3.10",
266 | "webpack-node-externals": "^1.7.2"
267 | },
268 | "dependencies": {
269 | "clean-css": "^4.2.1",
270 | "group-css-media-queries": "^1.4.1",
271 | "ignore": "^5.0.2",
272 | "impor": "^0.1.1",
273 | "less": "^4.1.0",
274 | "less-plugin-autoprefix": "^2.0.0",
275 | "less-plugin-functions": "^1.0.0",
276 | "mkpath": "^1.0.0",
277 | "sass.js": "^0.11.1",
278 | "typescript": "^4.9.3",
279 | "uglify-js": "^3.7.2"
280 | }
281 | }
282 |
--------------------------------------------------------------------------------
/src/Configuration.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as vscode from 'vscode';
3 | import * as path from 'path';
4 |
5 |
6 |
7 | export function getGlobalOptions(filename: string, key: string = 'compile', projectDefault: object = {}):any {
8 | let filenamePath: path.ParsedPath = path.parse(filename);
9 | let defaultOptions = {
10 | plugins: [],
11 | rootFileInfo: getRootFileInfo(filenamePath),
12 | relativeUrls: false
13 | };
14 |
15 | let configuredOptions = vscode.workspace.getConfiguration("easycompile", vscode.Uri.parse(filename)).get(key);
16 | return Object.assign({}, projectDefault, defaultOptions, configuredOptions);
17 | }
18 |
19 | export function getRootFileInfo(parsedPath: path.ParsedPath) {
20 | parsedPath.ext = ".less";
21 | parsedPath.base = parsedPath.name + ".less";
22 |
23 | return {
24 | filename: parsedPath.base,
25 | currentDirectory: parsedPath.dir,
26 | relativeUrls: false,
27 | entryPath: parsedPath.dir + "/",
28 | rootpath: null,
29 | rootFilename: null
30 | }
31 | }
32 |
33 | export function getNodeMPath() {
34 | return path.resolve(__dirname+'/../../node_modules/');
35 | }
36 |
37 | export function formatPath(path: string){
38 | //fix path on windows
39 | return path.replace(/^\/([a-zA-Z]+:\/)/g, "$1");
40 | }
41 |
42 | export function intepolatePath(this: void, path: string): string
43 | {
44 | if(vscode.workspace.workspaceFolders){
45 | let rootPath = vscode.workspace.workspaceFolders[0];
46 | path = formatPath((path).replace(/\$\{workspaceRoot\}/g, rootPath.uri.path));
47 | }
48 | return path;
49 | }
50 |
51 | export function resolveFilePath(this: void, main: string, tsPath: string, currentTsFile: string): string
52 | {
53 | const interpolatedFilePath: string = intepolatePath(main);
54 | const resolvedFilePath: string = path.resolve(tsPath, interpolatedFilePath);
55 | if (resolvedFilePath.indexOf(currentTsFile) >= 0)
56 | {
57 | return ''; // avoid infinite loops
58 | }
59 | return resolvedFilePath;
60 | }
61 |
62 |
63 |
64 | export function resolveMainFilePaths(this: void, main: string | string[], lessPath: string, currentLessFile: string): string[]
65 | {
66 | let mainFiles: string[];
67 | if (typeof main === "string")
68 | {
69 | mainFiles = [main];
70 | }
71 | else if (Array.isArray(main))
72 | {
73 | mainFiles = main;
74 | }
75 | else
76 | {
77 | mainFiles = [];
78 | }
79 |
80 | const interpolatedMainFilePaths: string[] = mainFiles.map(mainFile => intepolatePath(mainFile));
81 | const resolvedMainFilePaths: string[] = interpolatedMainFilePaths.map(mainFile => path.resolve(lessPath, mainFile));
82 | if (resolvedMainFilePaths.indexOf(currentLessFile) >= 0)
83 | {
84 | return []; // avoid infinite loops
85 | }
86 |
87 | return resolvedMainFilePaths;
88 | }
--------------------------------------------------------------------------------
/src/FileOptionsParser.ts:
--------------------------------------------------------------------------------
1 | const ARRAY_OPTS = {
2 | "main": true,
3 | "lib": true,
4 | };
5 |
6 | export function parse(line: string, defaults)
7 | {
8 | // does line start with a comment?: //
9 | let commentMatch: RegExpExecArray | null = /^\s*\/\/\s*(.+)/.exec(line);
10 | if (!commentMatch)
11 | {
12 | return defaults;
13 | }
14 |
15 | let options = Object.assign({}, defaults);
16 | let optionLine: string = commentMatch[1];
17 | let seenKeys: Object = {};
18 | for (let item of optionLine.split(',')) // string[]
19 | {
20 | let i: number = item.indexOf(':');
21 | if (i < 0)
22 | {
23 | continue;
24 | }
25 | let key: string = item.substr(0, i).trim();
26 |
27 | let value: string = item.substr(i + 1).trim();
28 | if (value.match(/^(""|''|true|false|undefined|null|[0-9]+)$/))
29 | {
30 | value = eval(value);
31 | }
32 |
33 | if (seenKeys[key] === true && ARRAY_OPTS[key])
34 | {
35 | let existingValue: any = options[key];
36 | if (!Array.isArray(existingValue))
37 | {
38 | existingValue = options[key] = [existingValue];
39 | }
40 | existingValue.push(value);
41 | }
42 | else
43 | {
44 | options[key] = value;
45 | seenKeys[key] = true;
46 | }
47 | }
48 |
49 | return options;
50 | }
--------------------------------------------------------------------------------
/src/StatusBarMessage.ts:
--------------------------------------------------------------------------------
1 | import {StatusBarMessageTypes} from "./StatusBarMessageTypes";
2 | import * as vscode from 'vscode';
3 |
4 | const ERROR_COLOR_CSS = "rgba(255,125,0,1)";
5 | const ERROR_DURATION_MS = 10000;
6 | const SUCCESS_DURATION_MS = 1500;
7 |
8 | const channel:vscode.OutputChannel = vscode.window.createOutputChannel("Easy Compile");
9 | // channel.show();
10 |
11 | let errorMessage: vscode.StatusBarItem | null;
12 |
13 | export function hideError()
14 | {
15 | if (errorMessage)
16 | {
17 | errorMessage.hide();
18 | errorMessage = null;
19 | }
20 | }
21 |
22 | export function show(message: string, type: StatusBarMessageTypes)
23 | {
24 | this.hideError();
25 |
26 | channel.appendLine(message);
27 |
28 | switch (type)
29 | {
30 | case StatusBarMessageTypes.SUCCESS:
31 | return vscode.window.setStatusBarMessage(message, SUCCESS_DURATION_MS);
32 |
33 | case StatusBarMessageTypes.INDEFINITE:
34 | return vscode.window.setStatusBarMessage(message);
35 |
36 | case StatusBarMessageTypes.ERROR:
37 | errorMessage = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 0);
38 | errorMessage.text = message;
39 | errorMessage.command = "workbench.action.showErrorsWarnings";
40 | errorMessage.color = ERROR_COLOR_CSS;
41 | errorMessage.show();
42 | setTimeout(hideError, ERROR_DURATION_MS);
43 |
44 | return errorMessage;
45 | }
46 | }
47 |
48 | export function output(message: string)
49 | {
50 | channel.appendLine(message);
51 | }
52 |
53 | export function getDiagnostic(error):vscode.Diagnostic
54 | {
55 | let message: string = 'Unknow error';
56 | let range: vscode.Range = new vscode.Range(0, 0, 0, 0);
57 |
58 | if (error.code)
59 | {
60 | switch (error.code)
61 | {
62 | case 'EACCES':
63 | case 'ENOENT':
64 | message = `Cannot open file '${error.path}'`;
65 | break;
66 | default:
67 | if(error.message) message = error.message;
68 | }
69 |
70 | channel.appendLine(message);
71 | }
72 | else if (error.line !== undefined && error.column !== undefined)
73 | {
74 | // typescript errors, try to highlight the affected range
75 | let lineIndex: number = error.line - 1;
76 | if(lineIndex<0) lineIndex = 0;
77 | range = new vscode.Range(lineIndex, error.column, lineIndex, 0);
78 | message = error.message;
79 | }else{
80 | console.log(error);
81 | }
82 |
83 | let diagnosis = new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Error);
84 |
85 | return diagnosis;
86 | }
87 |
88 | export function formatDiagnostic(diagnostic, file, alld)
89 | {
90 | file = file.replace(/^([a-zA-Z]+:\/)/g, "/$1");
91 | diagnostic.set(vscode.Uri.parse(file), alld);
92 | }
--------------------------------------------------------------------------------
/src/StatusBarMessageTypes.ts:
--------------------------------------------------------------------------------
1 |
2 | export const enum StatusBarMessageTypes
3 | {
4 | SUCCESS,
5 | INDEFINITE,
6 | ERROR
7 | }
--------------------------------------------------------------------------------
/src/compiles/less/CompileLessCommand.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | import * as Configuration from "../../Configuration";
4 | import * as StatusBarMessage from "../../StatusBarMessage";
5 | import {StatusBarMessageTypes} from "../../StatusBarMessageTypes";
6 |
7 | import * as LessCompiler from "./LessCompiler";
8 | // const impor = require('impor')(__dirname);
9 | // const LessCompiler = impor("./LessCompiler") as typeof import('./LessCompiler');
10 | export class CompileLessCommand
11 | {
12 | public constructor(
13 | private filePath: string,
14 | private lessDiagnosticCollection: vscode.DiagnosticCollection)
15 | {
16 | }
17 |
18 | public execute(callback = () => {})
19 | {
20 | StatusBarMessage.hideError();
21 |
22 | let globalOptions = Configuration.getGlobalOptions(this.filePath, 'less');
23 | let compilingMessage = StatusBarMessage.show("$(zap) Compiling less --> css", StatusBarMessageTypes.INDEFINITE);
24 | let startTime: number = Date.now();
25 | let renderPromise = LessCompiler.compile(this.filePath, globalOptions)
26 | .then(() =>
27 | {
28 | compilingMessage.dispose();
29 | let elapsedTime: number = (Date.now() - startTime);
30 | this.lessDiagnosticCollection.set(vscode.Uri.parse(this.filePath), []);
31 |
32 | StatusBarMessage.show(`$(check) Less compiled in ${elapsedTime}ms`, StatusBarMessageTypes.SUCCESS);
33 | callback();
34 | })
35 | .catch((error: any) =>
36 | {
37 | compilingMessage.dispose();
38 |
39 | let file:string;
40 | if(error.filename && this.filePath != error.filename){
41 | file = error.filename;
42 | }else{
43 | file = this.filePath;
44 | }
45 |
46 | StatusBarMessage.formatDiagnostic(this.lessDiagnosticCollection, file, [StatusBarMessage.getDiagnostic(error)]);
47 |
48 | StatusBarMessage.show("$(alert) Error compiling less (more detail in Errors and Warnings)", StatusBarMessageTypes.ERROR);
49 | callback();
50 | });
51 | }
52 | }
--------------------------------------------------------------------------------
/src/compiles/less/LessCompiler.ts:
--------------------------------------------------------------------------------
1 | import less from 'less'
2 | import * as mkpath from 'mkpath'
3 | import * as path from 'path'
4 | import * as fs from 'fs'
5 |
6 | import * as Configuration from "../../Configuration";
7 | import * as FileOptionsParser from "../../FileOptionsParser";
8 |
9 | const DEFAULT_EXT = ".css";
10 |
11 | // compile the given less file
12 | export function compile(lessFile: string, defaults): Promise
13 | {
14 | return readFilePromise(lessFile).then(buffer =>
15 | {
16 | const content: string = buffer.toString();
17 | const options = FileOptionsParser.parse(content, defaults);
18 | const lessPath: string = path.dirname(lessFile);
19 |
20 | // main is set: compile the referenced file instead
21 | if (options.main)
22 | {
23 | const mainFilePaths: string[] = Configuration.resolveMainFilePaths(options.main, lessPath, lessFile);
24 | if(!options.exclude) options.exclude = [];
25 | if(options.excludes) options.exclude = Object.assign([], options.exclude, options.excludes);
26 | const excludePaths: string[] = Configuration.resolveMainFilePaths(options.exclude, lessPath, lessFile);
27 | let lastPromise: Promise | null = null;
28 | if (mainFilePaths && mainFilePaths.length > 0)
29 | {
30 | for (const filePath of mainFilePaths)
31 | {
32 | if(filePath.indexOf('*')>-1){
33 | const paths = filePath.split('*');
34 | const subfiles = getSubFiles(paths[0], paths[1], excludePaths);
35 |
36 | for (const fileP of subfiles)
37 | {
38 | lastPromise = compilenext(fileP, defaults, lastPromise);
39 | }
40 | }else{
41 | lastPromise = compilenext(filePath, defaults, lastPromise);
42 | }
43 | }
44 | return lastPromise;
45 | }
46 | }
47 |
48 | // out
49 | if (options.out === null || options.out === false)
50 | {
51 | // is null or false: do not compile
52 | return null;
53 | }
54 |
55 | const out: string | boolean | undefined = options.out;
56 | const extension: string = chooseExtension(options);
57 | let cssRelativeFilename: string;
58 | const baseFilename: string = path.parse(lessFile).name;
59 |
60 | if (typeof out === "string")
61 | {
62 | // out is set: output to the given file name
63 | // check whether is a folder first
64 | let interpolatedOut = Configuration.intepolatePath(out);
65 |
66 | cssRelativeFilename = interpolatedOut;
67 | let lastCharacter = cssRelativeFilename.slice(-1);
68 | if (lastCharacter === '/' || lastCharacter === '\\')
69 | {
70 | cssRelativeFilename += baseFilename + extension;
71 | }
72 | else if (path.extname(cssRelativeFilename) === '')
73 | {
74 | cssRelativeFilename += extension;
75 | }
76 | }
77 | else
78 | {
79 | // out is not set: output to the same basename as the less file
80 | cssRelativeFilename = baseFilename + extension;
81 | }
82 |
83 | const cssFile = path.resolve(lessPath, cssRelativeFilename);
84 | delete options.out;
85 |
86 | // sourceMap
87 | let sourceMapFile: string;
88 | if (options.sourceMap)
89 | {
90 | // currently just has support for writing .map file to same directory
91 | const lessPath: string = path.parse(lessFile).dir;
92 | const cssPath: string = path.parse(cssFile).dir;
93 | const lessRelativeToCss: string = path.relative(cssPath, lessPath);
94 |
95 | const sourceMapOptions = {
96 | outputSourceFiles: false,
97 | sourceMapBasepath: lessPath,
98 | sourceMapFileInline: options.sourceMapFileInline,
99 | sourceMapRootpath: lessRelativeToCss,
100 | sourceMapURL: '',
101 | };
102 |
103 | if (!sourceMapOptions.sourceMapFileInline)
104 | {
105 | sourceMapFile = cssFile + '.map';
106 | // sourceMapOptions.sourceMapURL = "./" + baseFilename + extension + ".map";
107 | }
108 | options.sourceMap = sourceMapOptions;
109 | }
110 |
111 | // plugins
112 | options.plugins = [];
113 | if (options.sass2less !== false)
114 | {
115 | const LessPluginSass2less = require('../../plugins/pluginSass2Less');
116 | const sass2lessPlugin = new LessPluginSass2less();
117 | options.plugins.push(sass2lessPlugin);
118 | }
119 | if (options.functions !== false)
120 | {
121 | const LessPluginFunctions = require('less-plugin-functions');
122 | const pluginFunctions = new LessPluginFunctions();
123 | options.plugins.push(pluginFunctions);
124 | }
125 |
126 | if (options.autoprefixer)
127 | {
128 | const LessPluginAutoPrefix = require('less-plugin-autoprefix');
129 | const browsers: string[] = cleanBrowsersList(options.autoprefixer);
130 | const autoprefixPlugin = new LessPluginAutoPrefix({ browsers });
131 |
132 | options.plugins.push(autoprefixPlugin);
133 | }
134 |
135 | if (options.groupmedia)
136 | {
137 | const LessPluginGroupMedia = require('../../plugins/pluginGroup');
138 | const lessGroupPlugin = new LessPluginGroupMedia();
139 | options.plugins.push(lessGroupPlugin);
140 | }
141 |
142 | if (options.compress)
143 | {
144 | options.compress = false;
145 | const LessPluginCleanCSS = require('../../plugins/pluginCleanCss');
146 | const cleanCSSPlugin = new LessPluginCleanCSS({advanced: true});
147 | options.plugins.push(cleanCSSPlugin);
148 | }
149 |
150 | // set up the parser
151 | return less.render(content, options).then(output =>
152 | {
153 | if (options.sourceMap && output.map && sourceMapFile){
154 | const mapFileUrl: string = path.basename(sourceMapFile);
155 | output.css += '/*# sourceMappingURL='+mapFileUrl+' */';
156 | }
157 |
158 | return writeFileContents(cssFile, output.css).then(() =>
159 | {
160 | if (options.sourceMap && output.map && sourceMapFile)
161 | {
162 | return writeFileContents(sourceMapFile, output.map);
163 | }
164 | });
165 | });
166 | });
167 | }
168 |
169 | function cleanBrowsersList(autoprefixOption: string | string[]): string[]
170 | {
171 | let browsers: string[];
172 | if (Array.isArray(autoprefixOption))
173 | {
174 | browsers = autoprefixOption;
175 | }
176 | else
177 | {
178 | browsers = ("" + autoprefixOption).split(/,|;/);
179 | }
180 |
181 | return browsers.map(browser => browser.trim());
182 | }
183 |
184 | // writes a file's contents in a path where directories may or may not yet exist
185 | function writeFileContents(this: void, filepath: string, content: any): Promise
186 | {
187 | return new Promise((resolve, reject) =>
188 | {
189 | mkpath(path.dirname(filepath), err =>
190 | {
191 | if (err)
192 | {
193 | return reject(err);
194 | }
195 |
196 | fs.writeFile(filepath, content, err => err ? reject(err) : resolve());
197 | });
198 | });
199 | }
200 |
201 | function readFilePromise(this: void, filename: string): Promise
202 | {
203 | return new Promise((resolve, reject) =>
204 | {
205 | fs.readFile(filename, (err: any, buffer: Buffer) =>
206 | {
207 | if (err)
208 | {
209 | reject(err)
210 | }
211 | else
212 | {
213 | resolve(buffer);
214 | }
215 | });
216 | });
217 | }
218 |
219 | function chooseExtension(this: void, options): string
220 | {
221 | if (options && options.outExt)
222 | {
223 | if (options.outExt === "")
224 | {
225 | // special case for no extension (no idea if anyone would really want this?)
226 | return "";
227 | }
228 |
229 | return ensureDotPrefixed(options.outExt) || DEFAULT_EXT;
230 | }
231 |
232 | return DEFAULT_EXT;
233 | }
234 |
235 | function ensureDotPrefixed(this: void, extension: string): string
236 | {
237 | if (extension.startsWith("."))
238 | {
239 | return extension;
240 | }
241 |
242 | return extension ? `.${extension}` : "";
243 | }
244 |
245 | function getSubFiles(parent, file, excludePaths){
246 | let dirList = fs.readdirSync(parent);
247 | let paths:string[] = [];
248 | dirList.forEach(function(item){
249 | let p = path.join(parent, item);
250 | if(excludePaths.indexOf(p)<0){
251 | p = path.join(p, file);
252 | if(fs.existsSync(p)){
253 | paths.push(p);
254 | }
255 | }
256 | });
257 | return paths;
258 | }
259 |
260 | function promiseChainer(lastPromise: Promise, nextPromise: Promise): Promise{
261 | lastPromise.then(() => nextPromise);
262 | return nextPromise;
263 | }
264 |
265 | function compilenext(filePath, defaults, lastPromise): Promise{
266 | const mainPath: path.ParsedPath = path.parse(filePath);
267 | const mainRootFileInfo = Configuration.getRootFileInfo(mainPath);
268 | const mainDefaults = Object.assign({}, defaults, { rootFileInfo: mainRootFileInfo });
269 | const compilePromise = compile(filePath, mainDefaults);
270 | if (lastPromise)
271 | {
272 | lastPromise = promiseChainer(lastPromise, compilePromise);
273 | }
274 | else
275 | {
276 | lastPromise = compilePromise;
277 | }
278 | return lastPromise;
279 | }
--------------------------------------------------------------------------------
/src/compiles/sass/CompileSassCommand.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | import * as Configuration from "../../Configuration";
4 | import * as StatusBarMessage from "../../StatusBarMessage";
5 | import {StatusBarMessageTypes} from "../../StatusBarMessageTypes";
6 |
7 | import * as SassCompiler from "./SassCompiler";
8 | // const impor = require('impor')(__dirname);
9 | // const SassCompiler = impor("./SassCompiler") as typeof import('./SassCompiler');
10 |
11 | export class CompileSassCommand
12 | {
13 | public constructor(
14 | private filePath: string,
15 | private sassDiagnosticCollection: vscode.DiagnosticCollection)
16 | {
17 | }
18 |
19 | public execute(callback = () => {})
20 | {
21 | StatusBarMessage.hideError();
22 | let globalOptions = Configuration.getGlobalOptions(this.filePath, 'sass');
23 | let compilingMessage = StatusBarMessage.show("$(zap) Compiling sass --> css", StatusBarMessageTypes.INDEFINITE);
24 | let startTime: number = Date.now();
25 | let renderPromise = SassCompiler.compile(this.filePath, globalOptions)
26 | .then((sass: any) =>
27 | {
28 | compilingMessage.dispose();
29 | if(sass) sass.clearFiles();
30 | let elapsedTime: number = (Date.now() - startTime);
31 | this.sassDiagnosticCollection.set(vscode.Uri.parse(this.filePath), []);
32 |
33 | StatusBarMessage.show(`$(check) Sass compiled in ${elapsedTime}ms`, StatusBarMessageTypes.SUCCESS);
34 | callback();
35 | })
36 | .catch((error: any) =>
37 | {
38 | compilingMessage.dispose();
39 | if(error.sass) error.sass.clearFiles();
40 |
41 | let file:string;
42 | if(error.filename && this.filePath != error.filename){
43 | file = error.filename;
44 | }else{
45 | file = this.filePath;
46 | }
47 |
48 | StatusBarMessage.formatDiagnostic(this.sassDiagnosticCollection, file, [StatusBarMessage.getDiagnostic(error)]);
49 |
50 | StatusBarMessage.show("$(alert) Error compiling sass (more detail in Errors and Warnings)", StatusBarMessageTypes.ERROR);
51 | callback();
52 | });
53 | }
54 | }
--------------------------------------------------------------------------------
/src/compiles/sass/SassCompiler.ts:
--------------------------------------------------------------------------------
1 | import * as sassCompiler from 'sass.js';
2 | import * as mkpath from 'mkpath';
3 | import * as path from 'path';
4 | import * as fs from 'fs';
5 | import * as vscode from 'vscode';
6 |
7 | import * as Configuration from "../../Configuration";
8 | import * as FileOptionsParser from "../../FileOptionsParser";
9 |
10 | const DEFAULT_EXT = ".css";
11 |
12 | // compile the given sass file
13 | export function compile(sassFile: string, defaults): Promise
14 | {
15 | return readFilePromise(sassFile).then(buffer =>
16 | {
17 | const content: string = buffer.toString();
18 | const options = FileOptionsParser.parse(content, defaults);
19 | const sassPath: string = path.dirname(sassFile);
20 |
21 | // main is set: compile the referenced file instead
22 | if (options.main)
23 | {
24 | const mainFilePaths: string[] = Configuration.resolveMainFilePaths(options.main, sassPath, sassFile);
25 | if(!options.exclude) options.exclude = [];
26 | if(options.excludes) options.exclude = Object.assign([], options.exclude, options.excludes);
27 | const excludePaths: string[] = Configuration.resolveMainFilePaths(options.exclude, sassPath, sassFile);
28 | let lastPromise: Promise | null = null;
29 | if (mainFilePaths && mainFilePaths.length > 0)
30 | {
31 | for (const filePath of mainFilePaths)
32 | {
33 | if(filePath.indexOf('*')>-1){
34 | const paths = filePath.split('*');
35 | const subfiles = getSubFiles(paths[0], paths[1], excludePaths);
36 |
37 | for (const fileP of subfiles)
38 | {
39 | lastPromise = compilenext(fileP, defaults, lastPromise);
40 | }
41 | }else{
42 | lastPromise = compilenext(filePath, defaults, lastPromise);
43 | }
44 | }
45 | return lastPromise;
46 | }
47 | }
48 |
49 | // out
50 | if (options.out === null || options.out === false)
51 | {
52 | // is null or false: do not compile
53 | return null;
54 | }
55 |
56 | const out: string | boolean | undefined = options.out;
57 | const extension: string = chooseExtension(options);
58 | let cssRelativeFilename: string;
59 | const baseFilename: string = path.parse(sassFile).name;
60 |
61 | if (typeof out === "string")
62 | {
63 | // out is set: output to the given file name
64 | // check whether is a folder first
65 | let interpolatedOut = Configuration.intepolatePath(out);
66 |
67 | cssRelativeFilename = interpolatedOut;
68 | let lastCharacter = cssRelativeFilename.slice(-1);
69 | if (lastCharacter === '/' || lastCharacter === '\\')
70 | {
71 | cssRelativeFilename += baseFilename + extension;
72 | }
73 | else if (path.extname(cssRelativeFilename) === '')
74 | {
75 | cssRelativeFilename += extension;
76 | }
77 | }
78 | else
79 | {
80 | // out is not set: output to the same basename as the less file
81 | cssRelativeFilename = baseFilename + extension;
82 | }
83 |
84 | const cssFile = path.resolve(sassPath, cssRelativeFilename);
85 | const cssPath: string = path.parse(cssFile).dir;
86 | delete options.out;
87 | //need change "module.exports = factory()" to "module.exports = factory" in node_modules/sass.js/dist/sass.sync.js to make this work
88 | const sass = new sassCompiler();
89 | let opts = {
90 | style: sass.style.expanded
91 | };
92 |
93 | let sourceMapFile: string;
94 | let replaceList:any = {
95 | "stdin": path.relative(cssPath, sassFile)
96 | };
97 | if (options.sourceMap)
98 | {
99 | const sassRelativeToCss: string = path.relative(cssPath, sassPath);
100 |
101 | const sourceMapOptions = {
102 | sourceMapContents: false,
103 | sourceMapEmbed: options.sourceMapFileInline,
104 | sourceMapRoot: sassRelativeToCss
105 | // inputPath: path.basename(sassFile)
106 | };
107 |
108 | if(!options.sourceMapFileInline){
109 | sourceMapFile = cssFile + '.map';
110 | }
111 |
112 | opts = Object.assign({}, opts, sourceMapOptions);
113 | }
114 |
115 | sass._path = '/'+sassPath.replace(/\\/g, '/').replace(/\:/g, '')+'/';
116 | sass.importer(function(request, done) {
117 | if (request.path) {
118 | done();
119 | }else{
120 | let requestedPath = sassPath;
121 | if(request.previous != 'stdin')
122 | requestedPath = path.resolve(sassPath, path.dirname(request.previous));
123 | let paths = getPathVariations(request.current);
124 | let x, file;
125 | for(x in paths){
126 | let realPath = path.resolve(requestedPath, paths[x]);
127 | if(fileExists(realPath)){
128 | file = realPath;
129 | break;
130 | }
131 | }
132 | if (!file) {
133 | done({
134 | error: 'File "' + request.current + '" not found',
135 | });
136 | return;
137 | }
138 |
139 | readFilePromise(file).then(buffer =>
140 | {
141 | replaceList[request.current] = path.relative(sassPath, file);
142 | const content: string = buffer.toString();
143 | sass.writeFile(request.resolved, content, function() {
144 | done({
145 | path: replaceList[request.current],
146 | content: content
147 | });
148 | });
149 | }).catch((error: any) =>
150 | {
151 | done({
152 | error: error.message,
153 | });
154 | });
155 | }
156 | });
157 |
158 | return new Promise((resolve, reject) =>
159 | {
160 | sass.compile(content, opts, result => {
161 | if(result.status == 1){
162 | result.sass = sass;
163 | reject(result);
164 | }else{
165 | let css = result.text;
166 | let sourceMap;
167 | if(css){
168 | if (options.autoprefixer)
169 | {
170 | const LessPluginAutoPrefix = require('less-plugin-autoprefix');
171 | const browsers: string[] = cleanBrowsersList(options.autoprefixer);
172 | const autoprefixPlugin = new LessPluginAutoPrefix({ browsers });
173 |
174 | autoprefixPlugin.install(result, {
175 | addPostProcessor: function (postProcessor){
176 | css = postProcessor.process(css, {});
177 | }
178 | });
179 | }
180 |
181 | if (options.groupmedia)
182 | {
183 | const SassPluginGroupMedia = require('../../plugins/pluginGroup');
184 | const sassGroupPlugin = new SassPluginGroupMedia();
185 |
186 | sassGroupPlugin.install(result, {
187 | addPostProcessor: function (postProcessor){
188 | css = postProcessor.process(css, {});
189 | }
190 | });
191 | }
192 |
193 | if (options.compress)
194 | {
195 | options.compress = false;
196 | const LessPluginCleanCSS = require('../../plugins/pluginCleanCss');
197 | const cleanCSSPlugin = new LessPluginCleanCSS({advanced: true});
198 |
199 | cleanCSSPlugin.install(result, {
200 | addPostProcessor: function (postProcessor){
201 | css = postProcessor.process(css, {sourceMap: result.map?{
202 | getExternalSourceMap: function(){
203 | return JSON.stringify(result.map)
204 | },
205 | setExternalSourceMap: function(map){
206 | result.map = JSON.parse(map);
207 | },
208 | }:false, options: options});
209 | }
210 | });
211 | }
212 | if (options.sourceMap && result.map){
213 | sourceMap = result.map;
214 | let x;
215 | for(x in sourceMap.sources){
216 | let path = sourceMap.sources[x];
217 | if(replaceList[path]) sourceMap.sources[x] = replaceList[path];
218 | }
219 |
220 | if(sourceMapFile){
221 | const mapFileUrl: string = path.basename(sourceMapFile);
222 | css += "\n"+'/*# sourceMappingURL='+mapFileUrl+' */';
223 | }else{
224 | css += "\n"+'/*# sourceMappingURL=data:application/json;charset=utf-8;base64,'+Buffer.from(JSON.stringify(sourceMap)).toString("base64")+' */';
225 | }
226 | }
227 | }else{
228 | css = "";
229 | }
230 |
231 | return writeFileContents(cssFile, css).then(() =>
232 | {
233 | if (sourceMap && sourceMapFile)
234 | {
235 | return writeFileContents(sourceMapFile, JSON.stringify(sourceMap)).then(() => {
236 | resolve(sass);
237 | });
238 | }else{
239 | resolve(sass);
240 | }
241 | });
242 | }
243 | });
244 | });
245 | });
246 | }
247 |
248 | function fileExists(path) {
249 | try {
250 | var stat = fs.statSync(path);
251 | return stat && stat.isFile();
252 | }catch(err) {
253 | }
254 | return false;
255 | }
256 |
257 | function getPathVariations(currentPath) {
258 | // [importer,include_path] this is where we would add the ability to
259 | // examine the include_path (if we ever use that in Sass.js)
260 | currentPath = path.normalize(currentPath);
261 | var directory = path.dirname(currentPath);
262 |
263 | var basename = path.basename(currentPath);
264 | var extensions = ['.scss', '.sass', '.css'];
265 | // basically what is done by resolve_and_load() in file.cpp
266 | // Resolution order for ambiguous imports:
267 | var list = [
268 | // (1) filename as given
269 | currentPath,
270 | // (2) underscore + given
271 | directory + path.sep + '_' + basename
272 | ].concat(extensions.map(function(extension) {
273 | // (3) underscore + given + extension
274 | return directory + path.sep + '_' + basename + extension;
275 | })).concat(extensions.map(function(extension) {
276 | // (4) given + extension
277 | return directory + path.sep + '_' + basename + extension;
278 | }));
279 |
280 | return list;
281 | };
282 |
283 |
284 | function cleanBrowsersList(autoprefixOption: string | string[]): string[]
285 | {
286 | let browsers: string[];
287 | if (Array.isArray(autoprefixOption))
288 | {
289 | browsers = autoprefixOption;
290 | }
291 | else
292 | {
293 | browsers = ("" + autoprefixOption).split(/,|;/);
294 | }
295 |
296 | return browsers.map(browser => browser.trim());
297 | }
298 |
299 | // writes a file's contents in a path where directories may or may not yet exist
300 | function writeFileContents(this: void, filepath: string, content: any): Promise
301 | {
302 | return new Promise((resolve, reject) =>
303 | {
304 | mkpath(path.dirname(filepath), err =>
305 | {
306 | if (err)
307 | {
308 | return reject(err);
309 | }
310 |
311 | fs.writeFile(filepath, content, err => err ? reject(err) : resolve());
312 | });
313 | });
314 | }
315 |
316 | function readFilePromise(this: void, filename: string): Promise
317 | {
318 | return new Promise((resolve, reject) =>
319 | {
320 | fs.readFile(filename, (err: any, buffer: Buffer) =>
321 | {
322 | if (err)
323 | {
324 | reject(err)
325 | }
326 | else
327 | {
328 | resolve(buffer);
329 | }
330 | });
331 | });
332 | }
333 |
334 | function chooseExtension(this: void, options): string
335 | {
336 | if (options && options.outExt)
337 | {
338 | if (options.outExt === "")
339 | {
340 | // special case for no extension (no idea if anyone would really want this?)
341 | return "";
342 | }
343 |
344 | return ensureDotPrefixed(options.outExt) || DEFAULT_EXT;
345 | }
346 |
347 | return DEFAULT_EXT;
348 | }
349 |
350 | function ensureDotPrefixed(this: void, extension: string): string
351 | {
352 | if (extension.startsWith("."))
353 | {
354 | return extension;
355 | }
356 |
357 | return extension ? `.${extension}` : "";
358 | }
359 |
360 | function getSubFiles(parent, file, excludePaths){
361 | let dirList = fs.readdirSync(parent);
362 | let paths:string[] = [];
363 | dirList.forEach(function(item){
364 | let p = path.join(parent, item);
365 | if(excludePaths.indexOf(p)<0){
366 | p = path.join(p, file);
367 | if(fs.existsSync(p)){
368 | paths.push(p);
369 | }
370 | }
371 | });
372 | return paths;
373 | }
374 |
375 | function promiseChainer(lastPromise: Promise, nextPromise: Promise): Promise{
376 | lastPromise.then(() => nextPromise);
377 | return nextPromise;
378 | }
379 |
380 | function compilenext(filePath, defaults, lastPromise): Promise{
381 | const mainPath: path.ParsedPath = path.parse(filePath);
382 | const mainRootFileInfo = Configuration.getRootFileInfo(mainPath);
383 | const mainDefaults = Object.assign({}, defaults, { rootFileInfo: mainRootFileInfo });
384 | const compilePromise = compile(filePath, mainDefaults);
385 | if (lastPromise)
386 | {
387 | lastPromise = promiseChainer(lastPromise, compilePromise);
388 | }
389 | else
390 | {
391 | lastPromise = compilePromise;
392 | }
393 | return lastPromise;
394 | }
--------------------------------------------------------------------------------
/src/compiles/typescript/CompileTsCommand.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import * as path from 'path';
3 |
4 | import * as Configuration from "../../Configuration";
5 | import * as StatusBarMessage from "../../StatusBarMessage";
6 | import {StatusBarMessageTypes} from "../../StatusBarMessageTypes";
7 | import * as TsCompiler from "./TsCompiler";
8 |
9 | const defaultOpts = {
10 | 'surround': '(function (define){ ${code} })(define)'
11 | };
12 | export class CompileTsCommand
13 | {
14 | public constructor(
15 | private filePath: string,
16 | private tsDiagnosticCollection: vscode.DiagnosticCollection)
17 | {
18 | }
19 |
20 | public execute(callback = () => {})
21 | {
22 | StatusBarMessage.hideError();
23 |
24 | let globalOptions = Configuration.getGlobalOptions(this.filePath, 'typescript', defaultOpts);
25 | let compilingMessage = StatusBarMessage.show("$(zap) Compiling ts --> js", StatusBarMessageTypes.INDEFINITE);
26 | let startTime: number = Date.now();
27 | let renderPromise = TsCompiler.compile(this.filePath, globalOptions)
28 | .then((allDiagnostics) =>
29 | {
30 | compilingMessage.dispose();
31 | if(allDiagnostics !== null){
32 | if(allDiagnostics.length>0){
33 | let files = {};
34 | allDiagnostics.forEach(diagnostic => {
35 | let file = diagnostic.filename?diagnostic.filename:this.filePath;
36 | if(!files[file]) files[file] = [];
37 | files[file].push(StatusBarMessage.getDiagnostic(diagnostic));
38 | });
39 |
40 | for (const key in files) {
41 | const alld = files[key];
42 | StatusBarMessage.formatDiagnostic(this.tsDiagnosticCollection, key, alld);
43 | }
44 |
45 | StatusBarMessage.show("$(alert) Error compiling typescript (more detail in Errors and Warnings)", StatusBarMessageTypes.ERROR);
46 | }else{
47 | let elapsedTime: number = (Date.now() - startTime);
48 | this.tsDiagnosticCollection.set(vscode.Uri.parse(this.filePath), []);
49 |
50 | StatusBarMessage.show(`$(check) Typescript compiled in ${elapsedTime}ms`, StatusBarMessageTypes.SUCCESS);
51 | }
52 | }
53 | else {
54 | StatusBarMessage.show(`$(check) Typescript compiling disabled`, StatusBarMessageTypes.SUCCESS);
55 | }
56 | callback();
57 | })
58 | .catch((error: any) =>
59 | {
60 | compilingMessage.dispose();
61 |
62 | let file:string;
63 | if(error.filename && this.filePath != error.filename){
64 | file = error.filename;
65 | }else{
66 | file = this.filePath;
67 | }
68 |
69 | StatusBarMessage.formatDiagnostic(this.tsDiagnosticCollection, file, [StatusBarMessage.getDiagnostic(error)]);
70 |
71 | StatusBarMessage.show("$(alert) Error compiling typescript (more detail in Errors and Warnings)", StatusBarMessageTypes.ERROR);
72 | callback();
73 | });
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/compiles/typescript/TsCompiler.ts:
--------------------------------------------------------------------------------
1 | import * as ts from "typescript"
2 | import * as path from 'path'
3 | import * as fs from 'fs'
4 | import * as vscode from 'vscode';
5 |
6 | import * as Configuration from "../../Configuration";
7 | import * as FileOptionsParser from "../../FileOptionsParser";
8 | import {MinifyJsCommand} from "../../minify/js/MinifyJsCommand";
9 | import { isArray } from "util"
10 |
11 | const DEFAULT_EXT = ".js";
12 |
13 | // compile the given ts file
14 | export function compile(tsFile: string, defaults): Promise
15 | {
16 | return readFilePromise(tsFile).then(buffer =>
17 | {
18 | const content: string = buffer.toString();
19 | const options = FileOptionsParser.parse(content, defaults);
20 | const tsPath: string = path.dirname(tsFile);
21 |
22 | // main is set: compile the referenced file instead
23 | if (options.main)
24 | {
25 | const mainFilePaths: string[] = Configuration.resolveMainFilePaths(options.main, tsPath, tsFile);
26 | if(!options.exclude) options.exclude = [];
27 | if(options.excludes) options.exclude = Object.assign([], options.exclude, options.excludes);
28 | const excludePaths: string[] = Configuration.resolveMainFilePaths(options.exclude, tsPath, tsFile);
29 | let lastPromise: Promise | null = null;
30 | if (mainFilePaths && mainFilePaths.length > 0)
31 | {
32 | for (const filePath of mainFilePaths)
33 | {
34 | if(filePath.indexOf('*')>-1){
35 | const paths = filePath.split('*');
36 | const subfiles = getSubFiles(paths[0], paths[1], excludePaths);
37 | for (const fileP of subfiles)
38 | {
39 | lastPromise = compilenext(fileP, defaults, lastPromise);
40 | }
41 | }else{
42 | lastPromise = compilenext(filePath, defaults, lastPromise);
43 | }
44 | }
45 | return lastPromise;
46 | }
47 | }
48 |
49 | // out
50 | if ( typeof options.outfile !== "string"
51 | && typeof options.outdir !== "string")
52 | {
53 | // is null or false: do not compile
54 | return null;
55 | }
56 |
57 | const outfile: string | undefined = options.outfile;
58 | const outdir: string | undefined = options.outdir;
59 |
60 | const baseFilename: string = path.parse(tsFile).name;
61 | const pathToTypes = path.resolve(Configuration.intepolatePath('${workspaceRoot}/node_modules/@types'));
62 | let tsOptions:any = {
63 | noEmitOnError: true, noImplicitAny: false, sourceMap: false,
64 | allowJs: true, removeComments: true, listEmittedFiles: true,
65 | target: ts.ScriptTarget.ES5, module: ts.ModuleKind.AMD,
66 | typeRoots: [pathToTypes]
67 | }
68 | tsOptions = Object.assign({}, tsOptions, options);
69 | if (typeof outfile === "string")
70 | {
71 | tsOptions = Object.assign({}, tsOptions, {outFile: Configuration.resolveFilePath(outfile, tsPath, tsFile)});
72 | }
73 | else if (typeof outdir === "string")
74 | {
75 | tsOptions = Object.assign({}, tsOptions, {outDir: Configuration.resolveFilePath(outdir, tsPath, tsFile)});
76 | }
77 |
78 | if(isArray(tsOptions.lib)){
79 | tsOptions.lib.forEach((o, i, a) => o.indexOf('.d.')>-1?null:a[i]="lib."+o+".d.ts");
80 | }
81 |
82 | let program = ts.createProgram([tsFile], tsOptions);
83 | let emitResult = program.emit();
84 |
85 | let allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
86 | let alld: Array