Developing Dart Web Applications
Contents
Core Constraints
- Use
package:web: Always preferpackage:webover the legacydart:html,dart:js, ordart:js_utillibraries. - Avoid
dart:mirrors: Never usedart:mirrorsin web applications, as it is completely unsupported in Dart web compilation. - Use
dart:js_interop: Implement all JavaScript interoperability using thedart:js_interoplibrary. - Prefer Extension Types: Define JavaScript interop boundaries and complex JS objects using Dart
extension typedeclarations combined with@JSannotations.
JavaScript Interoperability
Implement JavaScript interoperability to seamlessly integrate JS libraries and browser APIs into Dart web apps.
- Annotate libraries or external declarations with
@JS()to bind them to JavaScript objects. - Use
extension typeto wrapJSObjector other JS types. This provides a zero-cost abstraction boundary for JS interop. - Use specific JS types provided by
dart:js_interop(e.g.,JSString,JSNumber,JSObject,JSAny) instead of native Dart types (String,int) when crossing the interop boundary.
Web Tooling & Environment
Manage the development lifecycle, compilation, and testing using webdev and build_runner.
- Dependencies: Ensure
build_runnerandbuild_web_compilersare listed underdev_dependenciesin thepubspec.yaml. If testing, includebuild_test. - Local Development: Use
webdev serveto launch a development server. This utilizes the development compiler, supporting incremental updates and fast refresh. - Debugging: Append the
--debugflag towebdev serveto enable Dart DevTools. - Production Build: Use
webdev buildto generate a minified, deployable JavaScript application.
Workflows
Workflow: Setting up a Dart Web Project
Copy and complete this checklist when initializing or configuring a Dart web project:
- Add required dev dependencies:
dart pub add build_runner build_web_compilers --dev - Add
package:webdependency:dart pub add web - Install webdev globally:
dart pub global activate webdev - Verify
pubspec.yamlcontains the correct dependencies. - Run
dart pub getto synchronize dependencies.
Workflow: Implementing JS Interop
Follow this sequence when binding a new JavaScript library or object:
- Import
dart:js_interop. - Add the
@JS()annotation to the library or specific external function/class. - Define the JS object boundary using
extension type Name._(JSObject _) implements JSObject. - Declare
externalmethods and properties inside the extension type, usingdart:js_interoptypes (e.g.,JSString). - Run validator -> review compilation errors -> fix type mismatches between Dart and JS types.
Workflow: Compiling and Serving
Apply conditional logic based on the deployment target:
- If developing locally:
- Run webdev serve (default port 8080). - For DevTools, run webdev serve --debug.
- If testing:
- Run dart run build_runner test -- -p chrome.
- If building for production:
- Run webdev build --output web:build to compile the web directory into the build directory.
Examples
High-Fidelity JS Interop Implementation
This example demonstrates the correct usage of dart:js_interop, package:web, and extension types to bind a hypothetical JavaScript UserAuth object.
@JS()
library user_auth_interop;
import 'dart:js_interop';
import 'package:web/web.dart' as web;
// Bind to a global JavaScript function
@JS('console.log')
external void _log(JSAny? message);
// Define the JS interop boundary using an extension type
@JS('UserAuth')
extension type UserAuth._(JSObject _) implements JSObject {
// External constructor
external UserAuth(JSString apiKey);
// External properties using JS types
external JSString get currentUser;
external set currentUser(JSString value);
// External methods
external void login(JSString username, JSString password);
external JSBoolean isLoggedIn();
}
void main() {
// Interact with the DOM using package:web
final web.HTMLDivElement appDiv = web.document.querySelector('#app') as web.HTMLDivElement;
appDiv.text = 'Initializing Auth...';
// Instantiate and use the JS interop object
final auth = UserAuth('api_key_123'.toJS);
auth.login('admin'.toJS, 'password'.toJS);
if (auth.isLoggedIn().toDart) {
_log('User logged in successfully!'.toJS);
appDiv.text = 'Welcome, ${auth.currentUser.toDart}';
}
}