Skip to content

Commit 20bcf32

Browse files
Add sass task
1 parent e048c0e commit 20bcf32

File tree

17 files changed

+944
-7
lines changed

17 files changed

+944
-7
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,6 @@ test_reports/
2828
/test_fixtures/dart_x_only/doc/api/
2929
/test_fixtures/docs/docs/doc/api/
3030
/test_fixtures/format/changes_needed_temp/
31-
/test_fixtures/needs_build_runner/.dart_tool
31+
/test_fixtures/needs_build_runner/.dart_tool
32+
/test_fixtures/sass/fake_consumer_package
33+
/test_fixtures/coverage/functional_test/pubspec.lock

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ development requirements:
2727
- Consistent code formatting
2828
- Static analysis to detect issues
2929
- Documentation generation
30+
- CSS generation using the Dart Sass compiler
3031
- Examples for manual testing/exploration
3132
- Applying a LICENSE file to all source files
3233
- Running dart unit tests on Sauce Labs
@@ -85,6 +86,7 @@ local tasks.
8586
- **Coverage:** collects coverage over test suites (unit, integration, and functional) and generates a report. Uses the [`coverage` package](https://github.com/dart-lang/coverage).
8687
- **Code Formatting:** runs the [`dartfmt` tool from the `dart_style` package](https://github.com/dart-lang/dart_style) over source code.
8788
- **Static Analysis:** runs the [`dartanalyzer`](https://www.dartlang.org/tools/analyzer/) over source code.
89+
- **Compiling Sass:** runs the [`compile_sass` tool from the `w_common` package](https://github.com/workiva/w_common).
8890
- **Applying a License to Source Files:** copies a LICENSE file to all applicable files.
8991
- **Generate a test runner file:** that allows for faster test execution.
9092
- **Running dart unit tests on Sauce Labs:** compiles dart unit tests that can be run in the browser and executes them on various platforms using Sauce Labs.
@@ -240,6 +242,7 @@ ddev format
240242
ddev gen-test-runner
241243
ddev task-runner
242244
ddev test
245+
ddev sass
243246
244247
# without the alias
245248
pub run dart_dev analyze
@@ -249,6 +252,7 @@ pub run dart_dev format
249252
pub run dart_dev gen-test-runner
250253
pub run dart_dev task-runner
251254
pub run dart_dev test
255+
pub run dart_dev sass
252256
```
253257

254258
Add the `-h` flag to any of the above commands to receive additional help

bin/compile_sass_proxy.dart

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2019 Workiva Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/// A proxy for the `compile_sass` executable from w_common.
16+
///
17+
/// Present so that dart_dev consumers do not have to have a direct dependency
18+
/// on w_common in order to run `pub run dart_dev sass`.
19+
///
20+
/// __Do not run directly - run `pub run dart_dev sass` instead.__
21+
library dart_dev.bin.compile_sass_proxy;
22+
23+
import 'dart:io';
24+
25+
import 'package:dart_dev/src/tasks/sass/api.dart'
26+
show intentionalInternalProxyArg;
27+
import 'package:dart_dev/util.dart' show reporter;
28+
import 'package:w_common/sass.dart' as wc_compile_sass;
29+
30+
main(List<String> args) async {
31+
if (!args.contains(intentionalInternalProxyArg)) {
32+
exitCode = 1;
33+
reporter.error(
34+
'[UNSUPPORTED]: Do not run `pub run dart_dev compile_sass_proxy` directly.\n\n'
35+
'Run `pub run dart_dev sass` instead.');
36+
return;
37+
}
38+
39+
args.remove(intentionalInternalProxyArg);
40+
await wc_compile_sass.main(args);
41+
}

lib/src/dart_dev_cli.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'dart:async';
1818
import 'dart:io';
1919

2020
import 'package:args/args.dart';
21+
import 'package:dart_dev/src/tasks/sass/cli.dart';
2122
import 'package:dart_dev/util.dart' show reporter;
2223

2324
import 'package:dart_dev/src/tasks/cli.dart';
@@ -63,6 +64,7 @@ dev(List<String> args) async {
6364
registerTask(new InitCli(), config.init);
6465
registerTask(new TaskRunnerCli(), config.taskRunner);
6566
registerTask(new TestCli(), config.test);
67+
registerTask(new SassCli(), config.sass);
6668

6769
try {
6870
LocalCli.discover(config.local.taskPaths).forEach((task, exec) {

lib/src/tasks/config.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import 'package:dart_dev/src/tasks/dart_x_only/config.dart';
2222
import 'package:dart_dev/src/tasks/format/config.dart';
2323
import 'package:dart_dev/src/tasks/gen_test_runner/config.dart';
2424
import 'package:dart_dev/src/tasks/init/config.dart';
25+
import 'package:dart_dev/src/tasks/sass/config.dart';
2526
import 'package:dart_dev/src/tasks/task_runner/config.dart';
2627
import 'package:dart_dev/src/tasks/test/config.dart';
2728
import 'package:dart_dev/src/tasks/local/config.dart';
@@ -41,6 +42,7 @@ class Config {
4142
InitConfig init = new InitConfig();
4243
TaskRunnerConfig taskRunner = new TaskRunnerConfig();
4344
TestConfig test = new TestConfig();
45+
SassConfig sass = new SassConfig();
4446

4547
Map<String, dynamic> toJson() => {
4648
'analyze': analyze,
@@ -53,6 +55,7 @@ class Config {
5355
'init': init,
5456
'taskRunner': taskRunner,
5557
'test': test,
58+
'sass': sass,
5659
};
5760
}
5861

lib/src/tasks/sass/api.dart

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright 2015 Workiva Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
library dart_dev.src.tasks.format.api;
16+
17+
import 'dart:async';
18+
19+
import 'package:args/args.dart';
20+
import 'package:dart_dev/src/tasks/sass/cli.dart';
21+
import 'package:meta/meta.dart';
22+
import 'package:w_common/sass.dart' as wc;
23+
import 'package:dart_dev/util.dart' show TaskProcess;
24+
25+
import 'package:dart_dev/src/tasks/task.dart';
26+
27+
const String intentionalInternalProxyArg = '--iprx=true';
28+
29+
SassTask sass({
30+
@required String sourceDir,
31+
@required String outputDir,
32+
@required List<String> watchDirs,
33+
bool release: false,
34+
bool preReleaseCheck: false,
35+
ArgResults parsedArgs,
36+
}) {
37+
var executable = 'pub';
38+
var args = <String>[
39+
'run',
40+
'dart_dev:compile_sass_proxy',
41+
intentionalInternalProxyArg,
42+
];
43+
44+
final reservedArgs = <String, dynamic>{
45+
'--${wc.sourceDirArg}': sourceDir,
46+
'--${wc.outputDirArg}': outputDir,
47+
'--${wc.watchDirsArg}': watchDirs,
48+
'--${wc.outputStyleArg}': release ? 'compressed' : 'expanded',
49+
// Make sure the file name matches the un-minified checked in source when the release option is set.
50+
'--${wc.compressedOutputStyleFileExtensionArg}': release
51+
? parsedArgs[wc.expandedOutputStyleFileExtensionArg]
52+
: parsedArgs[wc.compressedOutputStyleFileExtensionArg],
53+
};
54+
55+
reservedArgs.forEach((argName, argValue) {
56+
if (argValue != null) {
57+
if (argName == '--${wc.outputDirArg}' &&
58+
argValue == reservedArgs['--${wc.sourceDirArg}']) return;
59+
if (argName == '--${wc.compressedOutputStyleFileExtensionArg}' &&
60+
argValue == '--${wc.compressedOutputStyleFileExtensionDefaultValue}')
61+
return;
62+
if (argName == '--${wc.expandedOutputStyleFileExtensionArg}' &&
63+
argValue == '--${wc.expandedOutputStyleFileExtensionDefaultValue}')
64+
return;
65+
66+
args.add('$argName=$argValue');
67+
}
68+
});
69+
70+
for (var arg in parsedArgs.arguments
71+
.where((_arg) => !reservedArgs.keys.any((key) => _arg.startsWith(key)))) {
72+
if (arg == '--$releaseArgName' || arg == '-$releaseArgAbbr') continue;
73+
74+
args.add(arg);
75+
}
76+
77+
for (var arg in parsedArgs.rest
78+
.where((_arg) => !reservedArgs.keys.any((key) => _arg.startsWith(key)))) {
79+
args.add(arg);
80+
}
81+
82+
if (preReleaseCheck) {
83+
args
84+
..removeWhere((arg) => arg.contains('--${wc.outputStyleArg}'))
85+
..add('--${wc.outputStyleArg}=expanded')
86+
..add('--check');
87+
}
88+
89+
final process = new TaskProcess(executable, args);
90+
final task = new SassTask(
91+
'$executable ${args.join(' ')}', process.done.then((_) => null));
92+
process.stdout.listen(task._sassOutput.add);
93+
process.stderr.listen(task._sassOutput.addError);
94+
process.exitCode.then((code) {
95+
task.successful = code <= 0;
96+
});
97+
98+
return task;
99+
}
100+
101+
class SassTask extends Task {
102+
@override
103+
final Future<Null> done;
104+
final String sassCommand;
105+
106+
StreamController<String> _sassOutput = new StreamController();
107+
108+
SassTask(String this.sassCommand, this.done);
109+
110+
Stream<String> get sassOutput => _sassOutput.stream;
111+
}

lib/src/tasks/sass/cli.dart

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2019 Workiva Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
library dart_dev.src.tasks.sass.cli;
16+
17+
import 'dart:async';
18+
19+
import 'package:args/args.dart';
20+
import 'package:dart_dev/util.dart' show reporter;
21+
import 'package:w_common/sass.dart' as wc;
22+
23+
import 'package:dart_dev/src/tasks/sass/api.dart';
24+
import 'package:dart_dev/src/tasks/cli.dart';
25+
import 'package:dart_dev/src/tasks/config.dart';
26+
27+
const String releaseArgName = 'release';
28+
const String releaseArgAbbr = 'r';
29+
30+
class SassCli extends TaskCli {
31+
@override
32+
final ArgParser argParser = wc.sassCliArgs
33+
..addSeparator('-' * 80)
34+
..addFlag(releaseArgName,
35+
negatable: false,
36+
abbr: releaseArgAbbr,
37+
help:
38+
'Whether to compile minified CSS for bundling with a pub package. \n'
39+
'Typically only set during a CI run. \n'
40+
'A check of the unminified output will be performed first.');
41+
42+
@override
43+
final String command = 'sass';
44+
45+
@override
46+
String get usage =>
47+
'${super.usage} [.scss file(s)...] \n\n${argParser.usage}';
48+
49+
@override
50+
Future<CliResult> run(ArgResults parsedArgs, {bool color: true}) async {
51+
bool help = TaskCli.valueOf('help', parsedArgs, false);
52+
if (help) {
53+
print(usage);
54+
return new CliResult.success();
55+
}
56+
57+
String sourceDir =
58+
TaskCli.valueOf(wc.sourceDirArg, parsedArgs, config.sass.sourceDir);
59+
String outputDir = TaskCli.valueOf(
60+
wc.outputDirArg, parsedArgs, config.sass.outputDir ?? sourceDir);
61+
List<String> watchDirs =
62+
TaskCli.valueOf(wc.watchDirsArg, parsedArgs, config.sass.watchDirs);
63+
bool release =
64+
TaskCli.valueOf(releaseArgName, parsedArgs, config.sass.release);
65+
66+
if (release && !parsedArgs['help']) {
67+
// If running in "release mode", we want to first run a check on the committed (unminified) CSS output
68+
// and then immediately run it again to generate a minified copy using the same .scss sources to be
69+
// tar'd up for pub package assets in CI
70+
SassTask checkTask = sass(
71+
sourceDir: sourceDir,
72+
outputDir: outputDir,
73+
watchDirs: watchDirs,
74+
release: false,
75+
preReleaseCheck: true,
76+
parsedArgs: parsedArgs,
77+
);
78+
if (checkTask.sassCommand != null) {
79+
reporter.logGroup(checkTask.sassCommand,
80+
outputStream: checkTask.sassOutput);
81+
}
82+
await checkTask.done;
83+
84+
if (!checkTask.successful) return new CliResult.fail();
85+
}
86+
87+
SassTask task = sass(
88+
sourceDir: sourceDir,
89+
outputDir: outputDir,
90+
watchDirs: watchDirs,
91+
release: release,
92+
parsedArgs: parsedArgs,
93+
);
94+
if (task.sassCommand != null) {
95+
reporter.logGroup(task.sassCommand, outputStream: task.sassOutput);
96+
}
97+
await task.done;
98+
99+
if (!task.successful) return new CliResult.fail('Sass compilation failed.');
100+
101+
return new CliResult.success('Sass compilation completed successfully');
102+
}
103+
}

lib/src/tasks/sass/config.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2019 Workiva Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
library dart_dev.src.tasks.sass.config;
16+
17+
import 'package:w_common/sass.dart' as wc;
18+
19+
import 'package:dart_dev/src/tasks/config.dart';
20+
21+
class SassConfig extends TaskConfig {
22+
String sourceDir = wc.sourceDirDefaultValue;
23+
String outputDir;
24+
List<String> watchDirs;
25+
bool release = false;
26+
27+
Map<String, dynamic> toJson() => {
28+
'sourceDir': sourceDir,
29+
'outputDir': outputDir,
30+
'watchDirs': watchDirs,
31+
'minify': false,
32+
};
33+
}

pubspec.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ dependencies:
1515
args: '>=0.13.7 <2.0.0'
1616
dart2_constant: ^1.0.1
1717
path: ^1.3.6
18+
meta: ^1.0.0
1819
resource: '>=1.0.0 <3.0.0'
1920
uuid: '>=0.5.0 <2.0.0'
21+
w_common: ^1.19.0
2022
yaml: ^2.1.0
2123

2224
dev_dependencies:
2325
coverage: '>=0.9.3 <0.13.0'
2426
dart_style: ^1.0.7
2527
dependency_validator: ^1.1.0
28+
glob: ^1.1.7
2629
test: '>=0.12.24 <2.0.0'
2730

2831
executables:
@@ -34,3 +37,4 @@ environment:
3437
transformers:
3538
- test/pub_serve:
3639
$include: test/**_test{.*,}.dart
40+

0 commit comments

Comments
 (0)