mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

### Context: DDC modules are abstractions over how libraries are loaded/updated. The entirety of google3 uses the DDC/legacy module system due to its flexibility extensibility over the other two (ES6 and AMD/RequireJS). Unifying DDC's module system saves us from duplicating work and will allow us to have finer grained control over how JS modules are loaded. This is a a prerequisite to features such as hot reload. ### Overview: This change plumbs a boolean flag through flutter_tools that switches between DDC (new) and AMD (current) modules. This mode is automatically applied when `--extra-front-end-options=--dartdevc-module-format=ddc` is specified alongside `flutter run`. Other important additions include: * Splitting Flutter artifacts between DDC and AMD modules * Adding unit tests for the DDC module system * Additional bootstrapper logic for the DDC module system We don't expect to see any user-visible behavior or performance differences. This is dependent on [incoming module system support in DWDS](https://github.com/dart-lang/webdev/pull/2295) and [additional artifacts in the engine](https://github.com/flutter/engine/pull/47783). This is part of a greater effort to deprecate the AMD module system: https://github.com/dart-lang/sdk/issues/52361
337 lines
13 KiB
Dart
337 lines
13 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter_tools/src/web/bootstrap.dart';
|
|
import 'package:package_config/package_config.dart';
|
|
|
|
import '../../src/common.dart';
|
|
|
|
void main() {
|
|
test('generateBootstrapScript embeds urls correctly', () {
|
|
final String result = generateBootstrapScript(
|
|
requireUrl: 'require.js',
|
|
mapperUrl: 'mapper.js',
|
|
generateLoadingIndicator: true,
|
|
);
|
|
// require js source is interpolated correctly.
|
|
expect(result, contains('"requireJs": "require.js"'));
|
|
expect(result, contains('requireEl.src = getTTScriptUrl("requireJs");'));
|
|
// stack trace mapper source is interpolated correctly.
|
|
expect(result, contains('"mapper": "mapper.js"'));
|
|
expect(result, contains('mapperEl.src = getTTScriptUrl("mapper");'));
|
|
// data-main is set to correct bootstrap module.
|
|
expect(result, contains('requireEl.setAttribute("data-main", "main_module.bootstrap");'));
|
|
});
|
|
|
|
test('generateBootstrapScript includes loading indicator', () {
|
|
final String result = generateBootstrapScript(
|
|
requireUrl: 'require.js',
|
|
mapperUrl: 'mapper.js',
|
|
generateLoadingIndicator: true,
|
|
);
|
|
expect(result, contains('"flutter-loader"'));
|
|
expect(result, contains('"indeterminate"'));
|
|
});
|
|
|
|
test('generateBootstrapScript does not include loading indicator', () {
|
|
final String result = generateBootstrapScript(
|
|
requireUrl: 'require.js',
|
|
mapperUrl: 'mapper.js',
|
|
generateLoadingIndicator: false,
|
|
);
|
|
expect(result, isNot(contains('"flutter-loader"')));
|
|
expect(result, isNot(contains('"indeterminate"')));
|
|
});
|
|
|
|
// https://github.com/flutter/flutter/issues/107742
|
|
test('generateBootstrapScript loading indicator does not trigger scrollbars', () {
|
|
final String result = generateBootstrapScript(
|
|
requireUrl: 'require.js',
|
|
mapperUrl: 'mapper.js',
|
|
generateLoadingIndicator: true,
|
|
);
|
|
|
|
// See: https://regexr.com/6q0ft
|
|
final RegExp regex = RegExp(r'(?:\.flutter-loader\s*\{)[^}]+(?:overflow\:\s*hidden;)[^}]+}');
|
|
|
|
expect(result, matches(regex), reason: '.flutter-loader must have overflow: hidden');
|
|
});
|
|
|
|
// https://github.com/flutter/flutter/issues/82524
|
|
test('generateMainModule removes timeout from requireJS', () {
|
|
final String result = generateMainModule(
|
|
entrypoint: 'foo/bar/main.js',
|
|
nullAssertions: false,
|
|
nativeNullAssertions: false,
|
|
);
|
|
|
|
// See: https://regexr.com/6q0kp
|
|
final RegExp regex = RegExp(
|
|
r'(?:require\.config\(\{)(?:.|\s(?!\}\);))*'
|
|
r'(?:waitSeconds\:\s*0[,]?)'
|
|
r'(?:(?!\}\);).|\s)*\}\);');
|
|
|
|
expect(result, matches(regex), reason: 'require.config must have a waitSeconds: 0 config entry');
|
|
});
|
|
|
|
test('generateMainModule hides requireJS injected by DDC', () {
|
|
final String result = generateMainModule(
|
|
entrypoint: 'foo/bar/main.js',
|
|
nullAssertions: false,
|
|
nativeNullAssertions: false,
|
|
);
|
|
expect(result, contains('''define._amd = define.amd;'''),
|
|
reason: 'define.amd must be preserved as _amd so users may restore it if needed.');
|
|
expect(result, contains('''delete define.amd;'''),
|
|
reason: "define.amd must be removed so packages don't attempt to use Dart's instance.");
|
|
});
|
|
|
|
test('generateMainModule embeds urls correctly', () {
|
|
final String result = generateMainModule(
|
|
entrypoint: 'foo/bar/main.js',
|
|
nullAssertions: false,
|
|
nativeNullAssertions: false,
|
|
);
|
|
// bootstrap main module has correct defined module.
|
|
expect(result, contains('define("main_module.bootstrap", ["foo/bar/main.js", "dart_sdk"], '
|
|
'function(app, dart_sdk) {'));
|
|
});
|
|
|
|
test('generateMainModule can set bootstrap name', () {
|
|
final String result = generateMainModule(
|
|
entrypoint: 'foo/bar/main.js',
|
|
nullAssertions: false,
|
|
nativeNullAssertions: false,
|
|
bootstrapModule: 'foo_module.bootstrap',
|
|
);
|
|
// bootstrap main module has correct defined module.
|
|
expect(result, contains('define("foo_module.bootstrap", ["foo/bar/main.js", "dart_sdk"], '
|
|
'function(app, dart_sdk) {'));
|
|
});
|
|
|
|
test('generateMainModule includes null safety switches', () {
|
|
final String result = generateMainModule(
|
|
entrypoint: 'foo/bar/main.js',
|
|
nullAssertions: true,
|
|
nativeNullAssertions: true,
|
|
);
|
|
|
|
expect(result, contains('''dart_sdk.dart.nonNullAsserts(true);'''));
|
|
expect(result, contains('''dart_sdk.dart.nativeNonNullAsserts(true);'''));
|
|
});
|
|
|
|
test('generateMainModule can disable null safety switches', () {
|
|
final String result = generateMainModule(
|
|
entrypoint: 'foo/bar/main.js',
|
|
nullAssertions: false,
|
|
nativeNullAssertions: false,
|
|
);
|
|
|
|
expect(result, contains('''dart_sdk.dart.nonNullAsserts(false);'''));
|
|
expect(result, contains('''dart_sdk.dart.nativeNonNullAsserts(false);'''));
|
|
});
|
|
|
|
test('generateTestBootstrapFileContents embeds urls correctly', () {
|
|
final String result = generateTestBootstrapFileContents('foo.dart.js', 'require.js', 'mapper.js');
|
|
|
|
expect(result, contains('el.setAttribute("data-main", \'foo.dart.js\');'));
|
|
});
|
|
|
|
test('generateTestEntrypoint does not generate test config wrappers when testConfigPath is not passed', () {
|
|
final String result = generateTestEntrypoint(
|
|
relativeTestPath: 'relative_path.dart',
|
|
absolutePath: 'absolute_path.dart',
|
|
testConfigPath: null,
|
|
languageVersion: LanguageVersion(2, 8),
|
|
);
|
|
|
|
expect(result, isNot(contains('test_config.testExecutable')));
|
|
});
|
|
|
|
test('generateTestEntrypoint generates test config wrappers when testConfigPath is passed', () {
|
|
final String result = generateTestEntrypoint(
|
|
relativeTestPath: 'relative_path.dart',
|
|
absolutePath: 'absolute_path.dart',
|
|
testConfigPath: 'test_config_path.dart',
|
|
languageVersion: LanguageVersion(2, 8),
|
|
);
|
|
|
|
expect(result, contains('test_config.testExecutable'));
|
|
});
|
|
|
|
test('generateTestEntrypoint embeds urls correctly', () {
|
|
final String result = generateTestEntrypoint(
|
|
relativeTestPath: 'relative_path.dart',
|
|
absolutePath: '/test/absolute_path.dart',
|
|
testConfigPath: null,
|
|
languageVersion: LanguageVersion(2, 8),
|
|
);
|
|
|
|
expect(result, contains("Uri.parse('file:///test/absolute_path.dart')"));
|
|
});
|
|
|
|
group('Using the DDC module system', () {
|
|
test('generateDDCBootstrapScript embeds urls correctly', () {
|
|
final String result = generateDDCBootstrapScript(
|
|
entrypoint: 'foo/bar/main.js',
|
|
ddcModuleLoaderUrl: 'ddc_module_loader.js',
|
|
mapperUrl: 'mapper.js',
|
|
generateLoadingIndicator: true,
|
|
);
|
|
// ddc module loader js source is interpolated correctly.
|
|
expect(result, contains('"moduleLoader": "ddc_module_loader.js"'));
|
|
expect(result, contains('"src": "ddc_module_loader.js"'));
|
|
// stack trace mapper source is interpolated correctly.
|
|
expect(result, contains('"mapper": "mapper.js"'));
|
|
expect(result, contains('"src": "mapper.js"'));
|
|
// data-main is set to correct bootstrap module.
|
|
expect(result, contains('"src": "main_module.bootstrap.js"'));
|
|
expect(result, contains('"id": "data-main"'));
|
|
});
|
|
|
|
test('generateDDCBootstrapScript initializes configuration objects', () {
|
|
final String result = generateDDCBootstrapScript(
|
|
entrypoint: 'foo/bar/main.js',
|
|
ddcModuleLoaderUrl: 'ddc_module_loader.js',
|
|
mapperUrl: 'mapper.js',
|
|
generateLoadingIndicator: true,
|
|
);
|
|
// LoadConfiguration and DDCLoader objects must be constructed.
|
|
expect(result, contains(r'new window.$dartLoader.LoadConfiguration('));
|
|
expect(result, contains(r'new window.$dartLoader.DDCLoader('));
|
|
// Specific fields must be set on the LoadConfiguration.
|
|
expect(result, contains('.bootstrapScript ='));
|
|
expect(result, contains('.loadScriptFn ='));
|
|
// DDCLoader.nextAttempt must be invoked to begin loading.
|
|
expect(result, contains('nextAttempt()'));
|
|
// Proper window objects are initialized.
|
|
expect(result, contains(r'window.$dartLoader.loadConfig ='));
|
|
expect(result, contains(r'window.$dartLoader.loader ='));
|
|
});
|
|
|
|
test('generateDDCBootstrapScript includes loading indicator', () {
|
|
final String result = generateDDCBootstrapScript(
|
|
entrypoint: 'foo/bar/main.js',
|
|
ddcModuleLoaderUrl: 'ddc_module_loader.js',
|
|
mapperUrl: 'mapper.js',
|
|
generateLoadingIndicator: true,
|
|
);
|
|
expect(result, contains('"flutter-loader"'));
|
|
expect(result, contains('"indeterminate"'));
|
|
});
|
|
|
|
test('generateDDCBootstrapScript does not include loading indicator', () {
|
|
final String result = generateDDCBootstrapScript(
|
|
entrypoint: 'foo/bar/main.js',
|
|
ddcModuleLoaderUrl: 'ddc_module_loader.js',
|
|
mapperUrl: 'mapper.js',
|
|
generateLoadingIndicator: false,
|
|
);
|
|
expect(result, isNot(contains('"flutter-loader"')));
|
|
expect(result, isNot(contains('"indeterminate"')));
|
|
});
|
|
|
|
// https://github.com/flutter/flutter/issues/107742
|
|
test('generateDDCBootstrapScript loading indicator does not trigger scrollbars', () {
|
|
final String result = generateDDCBootstrapScript(
|
|
entrypoint: 'foo/bar/main.js',
|
|
ddcModuleLoaderUrl: 'ddc_module_loader.js',
|
|
mapperUrl: 'mapper.js',
|
|
generateLoadingIndicator: true,
|
|
);
|
|
|
|
// See: https://regexr.com/6q0ft
|
|
final RegExp regex = RegExp(r'(?:\.flutter-loader\s*\{)[^}]+(?:overflow\:\s*hidden;)[^}]+}');
|
|
|
|
expect(result, matches(regex), reason: '.flutter-loader must have overflow: hidden');
|
|
});
|
|
|
|
test('generateDDCMainModule embeds the entrypoint correctly', () {
|
|
final String result = generateDDCMainModule(
|
|
entrypoint: 'main.js',
|
|
nullAssertions: false,
|
|
nativeNullAssertions: false,
|
|
);
|
|
// bootstrap main module has correct defined module.
|
|
expect(result, contains('let appName = "main.js"'));
|
|
expect(result, contains('let moduleName = "main.js"'));
|
|
expect(result, contains('dart_library.start(appName, uuid, moduleName, "main");'));
|
|
});
|
|
|
|
test('generateDDCMainModule embeds its exported main correctly', () {
|
|
final String result = generateDDCMainModule(
|
|
entrypoint: 'foo/bar/main.js',
|
|
nullAssertions: false,
|
|
nativeNullAssertions: false,
|
|
exportedMain: 'foo__bar__main'
|
|
);
|
|
// bootstrap main module has correct defined module.
|
|
expect(result, contains('let appName = "foo/bar/main.js"'));
|
|
expect(result, contains('let moduleName = "foo/bar/main.js"'));
|
|
expect(result, contains('dart_library.start(appName, uuid, moduleName, "foo__bar__main");'));
|
|
});
|
|
|
|
test('generateDDCMainModule includes null safety switches', () {
|
|
final String result = generateDDCMainModule(
|
|
entrypoint: 'main.js',
|
|
nullAssertions: true,
|
|
nativeNullAssertions: true,
|
|
);
|
|
|
|
expect(result, contains('''dart.nonNullAsserts(true);'''));
|
|
expect(result, contains('''dart.nativeNonNullAsserts(true);'''));
|
|
});
|
|
|
|
test('generateDDCMainModule can disable null safety switches', () {
|
|
final String result = generateDDCMainModule(
|
|
entrypoint: 'main.js',
|
|
nullAssertions: false,
|
|
nativeNullAssertions: false,
|
|
);
|
|
|
|
expect(result, contains('''dart.nonNullAsserts(false);'''));
|
|
expect(result, contains('''dart.nativeNonNullAsserts(false);'''));
|
|
});
|
|
|
|
test('generateTestBootstrapFileContents embeds urls correctly', () {
|
|
final String result = generateTestBootstrapFileContents('foo.dart.js', 'require.js', 'mapper.js');
|
|
|
|
expect(result, contains('el.setAttribute("data-main", \'foo.dart.js\');'));
|
|
});
|
|
|
|
test('generateTestEntrypoint does not generate test config wrappers when testConfigPath is not passed', () {
|
|
final String result = generateTestEntrypoint(
|
|
relativeTestPath: 'relative_path.dart',
|
|
absolutePath: 'absolute_path.dart',
|
|
testConfigPath: null,
|
|
languageVersion: LanguageVersion(2, 8),
|
|
);
|
|
|
|
expect(result, isNot(contains('test_config.testExecutable')));
|
|
});
|
|
|
|
test('generateTestEntrypoint generates test config wrappers when testConfigPath is passed', () {
|
|
final String result = generateTestEntrypoint(
|
|
relativeTestPath: 'relative_path.dart',
|
|
absolutePath: 'absolute_path.dart',
|
|
testConfigPath: 'test_config_path.dart',
|
|
languageVersion: LanguageVersion(2, 8),
|
|
);
|
|
|
|
expect(result, contains('test_config.testExecutable'));
|
|
});
|
|
|
|
test('generateTestEntrypoint embeds urls correctly', () {
|
|
final String result = generateTestEntrypoint(
|
|
relativeTestPath: 'relative_path.dart',
|
|
absolutePath: '/test/absolute_path.dart',
|
|
testConfigPath: null,
|
|
languageVersion: LanguageVersion(2, 8),
|
|
);
|
|
|
|
expect(result, contains("Uri.parse('file:///test/absolute_path.dart')"));
|
|
});
|
|
});
|
|
}
|