Giter Club home page Giter Club logo

source_gen's Introduction

Dart CI Pub Package Version Join the chat on Gitter

Overview

source_gen provides utilities for automated source code generation for Dart:

  • A framework for writing Builders that consume and produce Dart code.
  • A convention for human and tool generated Dart code to coexist with clean separation, and for multiple code generators to integrate in the same project.

Its main purpose is to expose a developer-friendly API on top of lower-level packages like the analyzer or build. You don't have to use source_gen in order to generate source code; we also expose a set of library APIs that might be useful in your generators.

Quick Start Guide for writing a Generator

Add a dependency on source_gen in your pubspec.

dependencies:
  source_gen:

If you're only using source_gen in your own project to generate code and you won't publish your Generator for others to use, it can be a dev_dependency:

dev_dependencies:
  source_gen:

Once you have source_gen setup, you should reference the examples below.

Writing a generator to output Dart source code

Extend the Generator or GeneratorForAnnotation class and source_gen will call your generator for a Dart library or for each element within a library tagged with the annotation you are interested in.

Configuring and Running generators

source_gen is based on the build package and exposes options for using your Generator in a Builder. Choose a Builder based on where you want the generated code to end up:

  • If you want to write to .g.dart files which are referenced as a part in the original source file, use SharedPartBuilder. This is the convention for generated code in part files, and this file may also contain code from Generators provided by other packages.
  • If you want to write to .some_name.dart files which are referenced as a part in the original source file, use PartBuilder. You should choose an extension unique to your package. Multiple Generators may output to this file, but they will all come from your package and you will set up the entire list when constructing the builder. Using the extension .g.dart may cause conflicts with other projects that use SharedPartBuilder since outputs must be unique.
  • If you want to write standalone Dart library which can be imported use LibraryBuilder. Only a single Generator may be used as a LibraryBuilder.

In order to get the Builder used with build_runner it must be configured in a build.yaml file. See build_config for more details. Whenever you are publishing a package that includes a build.yaml file you should include a dependency on build_config in your pubspec.

When using SharedPartBuilder it should always be configured to build_to: cache (hidden files) and apply the combining_builder from this package. The combining builder reads in all the pieces written by different shared part builders and writes them to the final .g.dart output in the user's source directory. You should never use the .g.dart extension for any other Builder.

builders:
  some_cool_builder:
    import: "package:this_package/builder.dart"
    builder_factories: ["someCoolBuilder"]
    # The `partId` argument to `SharedPartBuilder` is "some_cool_builder"
    build_extensions: {".dart": [".some_cool_builder.g.part"]}
    auto_apply: dependents
    build_to: cache
    # To copy the `.g.part` content into `.g.dart` in the source tree
    applies_builders: ["source_gen:combining_builder"]

Configuring combining_builder

ignore_for_file

Sometimes generated code does not support all of the lints specified in the target package. When using a Builder based on package:source_gen which applies combining_builder, set the ignore_for_file option to a list of lints you wish to be ignored in all generated libraries.

Example build.yaml configuration:

targets:
  $default:
    builders:
      source_gen:combining_builder:
        options:
          ignore_for_file:
          - lint_alpha
          - lint_beta

preamble

When using a Builder based on package:source_gen which applies combining_builder, set the preamble option to a string you wish to be prepended to all generated libraries.

Example build.yaml configuration:

targets:
  $default:
    builders:
      source_gen:combining_builder:
        options:
          preamble: |
                // Foo
                
                // Bar

Hint: When both ignore_for_file and preamble are used the generated libraries will contain the lints of ignore_for_file on top of the preamble.

If you provide a builder that uses SharedPartBuilder and combining_builder, you should document these features for your users.

Generating files in different directories

The output location for an input file can be changed:

  • when using PartBuilder or LibraryBuilder.
  • when using SharedPartBuilder which apply the combining_builder as part of the build.

By default, a .g.dart or .some_name.dart file is generated next to the input. To change this, set the build_extensions option on the corresponding builder. In the options, build_extensions is a map from String to String, where the key is matches inputs and the value is a single build output. For more details on build extensions, see the docs in the build package.

For example, you can use these options to generate files under lib/generated with the following build configuration:

targets:
  $default:
    builders:
      # A SharedPartBuilder which uses the combining builder
      source_gen:combining_builder:
        options:
          build_extensions:
            '^lib/{{}}.dart': 'lib/generated/{{}}.g.dart'

      # A PartBuilder or LibraryBuilder
      some_cool_builder:
        options:
          build_extensions:
            '^lib/models/{{}}.dart': 'lib/models/generated/{{}}.foo.dart'

Remember to change the part statement in the input to refer to the correct output file in the other directory.

Note that builder options are part of source_gen's public api! When using them in a build configuration, always add a dependency on source_gen as well:

dev_dependencies:
  source_gen: ^1.1.0

FAQ

What is the difference between source_gen and build?

Build is a platform-agnostic framework for Dart asset or code generation that is pluggable into build systems including bazel, and standalone tools like build_runner. You could also build your own.

Meanwhile, source_gen provides an API and tooling that is easily usable on top of build to make common tasks easier and more developer friendly. For example the PartBuilder class wraps one or more Generator instances to make a Builder which creates part of files, while the LibraryBuilder class wraps a single Generator to make a Builder which creates Dart library files.

Publishing automation

For information about our publishing automation and release process, see https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.

source_gen's People

Contributors

a14n avatar alex-misch avatar alorenzen avatar chalin avatar davidmorgan avatar dependabot[bot] avatar devoncarew avatar grouma avatar hypen-flutter avatar jakemac53 avatar johnsonmh avatar jtdlab avatar kevmoo avatar ldicarlo avatar letsar avatar manhluong avatar matanlurey avatar mattrberry avatar mbojey2 avatar means88 avatar michalsrutek avatar natebosch avatar samschendel avatar scheglov avatar simolus3 avatar sperochon avatar srawlins avatar stereotype441 avatar tayloranderson-wf avatar waffle-iron avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

source_gen's Issues

Broken by Dart 1.12-dev

See https://travis-ci.org/dart-lang/source_gen/jobs/69027291

AnalysisException: <rethrow>
  Caused by The null object does not have a getter 'importsAndExports'.

  NoSuchMethodError: method not found: 'importsAndExports'
  Receiver: null
  Arguments: []
  #0      AnalysisTask._safelyPerform (package:analyzer/src/generated/engine.dart:6434:7)
  #1      AnalysisTask.perform (package:analyzer/src/generated/engine.dart:6403:7)
  #2      AnalysisContextImpl._cacheDartResolutionData (package:analyzer/src/generated/engine.dart:2804:12)
  #3      AnalysisContextImpl._getDartResolutionData (package:analyzer/src/generated/engine.dart:3413:17)
  #4      AnalysisContextImpl._getDartResolutionData2 (package:analyzer/src/generated/engine.dart:3442:14)
  #5      AnalysisContextImpl.computeLibraryElement (package:analyzer/src/generated/engine.dart:1692:7)
  #6      _getLibraryElement (package:source_gen/src/utils.dart:170:20)
  #7      _getLibraryElements.<anonymous closure> (package:source_gen/src/utils.dart:183:20)
  #8      MappedListIterable.elementAt (dart:_internal/iterable.dart:413)
  #9      ListIterator.moveNext (dart:_internal/iterable.dart:341)
  #10     WhereIterator.moveNext (dart:_internal/iterable.dart:435)
  #11     List.List.from (dart:core-patch/array_patch.dart:44)
  #12     Iterable.toList (dart:core/iterable.dart:340)
  #13     getAnalysisContextForProjectPath.<getAnalysisContextForProjectPath_async_body> (package:source_gen/src/utils.dart:105:44)
  #14     Future.Future.microtask.<anonymous closure> (dart:async/future.dart:144)
  #15     StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:205:15)
  #16     StackZoneSpecification.registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:124:48)
  #17     _rootRun (dart:async/zone.dart:900)
  #18     _CustomZone.run (dart:async/zone.dart:803)
  #19     _CustomZone.runGuarded (dart:async/zone.dart:709)
  #20     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:734)
  #21     StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:205:15)
  #22     StackZoneSpecification.registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:124:48)
  #23     _rootRun (dart:async/zone.dart:904)
  #24     _CustomZone.run (dart:async/zone.dart:803)
  #25     _CustomZone.runGuarded (dart:async/zone.dart:709)
  #26     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:734)
  #27     _microtaskLoop (dart:async/schedule_microtask.dart:43)
  #28     _microtaskLoopEntry (dart:async/schedule_microtask.dart:52)
  #29     _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:96)
  #30     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:149)

  Caused by The null object does not have a getter 'importsAndExports'.

  NoSuchMethodError: method not found: 'importsAndExports'
  Receiver: null
  Arguments: []
  #0      Object._noSuchMethod (dart:core-patch/object_patch.dart:42)
  #1      Object.noSuchMethod (dart:core-patch/object_patch.dart:45)
  #2      LibraryResolver._addToDependencyMap (package:analyzer/src/generated/resolver.dart:7981:49)
  #3      LibraryResolver._addToDependencyMap (package:analyzer/src/generated/resolver.dart:7993:9)
  #4      LibraryResolver._addToDependencyMap (package:analyzer/src/generated/resolver.dart:7983:9)
  #5      LibraryResolver._computeDependencyMap (package:analyzer/src/generated/resolver.dart:8256:5)
  #6      LibraryResolver._computeLibrariesInCycles (package:analyzer/src/generated/resolver.dart:8299:9)
  #7      LibraryResolver.resolveLibrary (package:analyzer/src/generated/resolver.dart:7861:26)
  #8      ResolveDartLibraryTask.internalPerform (package:analyzer/src/generated/engine.dart:10538:15)
  #9      AnalysisTask._safelyPerform (package:analyzer/src/generated/engine.dart:6430:7)
  #10     AnalysisTask.perform (package:analyzer/src/generated/engine.dart:6403:7)
  #11     AnalysisContextImpl._cacheDartResolutionData (package:analyzer/src/generated/engine.dart:2804:12)
  #12     AnalysisContextImpl._getDartResolutionData (package:analyzer/src/generated/engine.dart:3413:17)
  #13     AnalysisContextImpl._getDartResolutionData2 (package:analyzer/src/generated/engine.dart:3442:14)
  #14     AnalysisContextImpl.computeLibraryElement (package:analyzer/src/generated/engine.dart:1692:7)
  #15     _getLibraryElement (package:source_gen/src/utils.dart:170:20)
  #16     _getLibraryElements.<anonymous closure> (package:source_gen/src/utils.dart:183:20)
  #17     MappedListIterable.elementAt (dart:_internal/iterable.dart:413)
  #18     ListIterator.moveNext (dart:_internal/iterable.dart:341)
  #19     WhereIterator.moveNext (dart:_internal/iterable.dart:435)
  #20     List.List.from (dart:core-patch/array_patch.dart:44)
  #21     Iterable.toList (dart:core/iterable.dart:340)
  #22     getAnalysisContextForProjectPath.<getAnalysisContextForProjectPath_async_body> (package:source_gen/src/utils.dart:105:44)
  #23     Future.Future.microtask.<anonymous closure> (dart:async/future.dart:144)
  #24     StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:205:15)
  #25     StackZoneSpecification.registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:124:48)
  #26     _rootRun (dart:async/zone.dart:900)
  #27     _CustomZone.run (dart:async/zone.dart:803)
  #28     _CustomZone.runGuarded (dart:async/zone.dart:709)
  #29     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:734)
  #30     StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:205:15)
  #31     StackZoneSpecification.registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:124:48)
  #32     _rootRun (dart:async/zone.dart:904)
  #33     _CustomZone.run (dart:async/zone.dart:803)
  #34     _CustomZone.runGuarded (dart:async/zone.dart:709)
  #35     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:734)
  #36     _microtaskLoop (dart:async/schedule_microtask.dart:43)
  #37     _microtaskLoopEntry (dart:async/schedule_microtask.dart:52)
  #38     _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:96)
  #39     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:149)


  package:analyzer/src/generated/engine.dart 2456:11            AnalysisContextImpl.recordResolveDartLibraryTaskResults
  package:analyzer/src/generated/engine.dart 4866:32            AnalysisContextImpl_AnalysisTaskResultRecorder.visitResolveDartLibraryTask
  package:analyzer/src/generated/engine.dart 10530:15           ResolveDartLibraryTask.accept
  package:analyzer/src/generated/engine.dart 6411:33            AnalysisTask.perform.<fn>
  package:analyzer/src/generated/utilities_general.dart 150:15  _PerformanceTagImpl.makeCurrentWhile
  package:analyzer/src/generated/engine.dart 6411:10            AnalysisTask.perform
  package:analyzer/src/generated/engine.dart 2804:12            AnalysisContextImpl._cacheDartResolutionData
  package:analyzer/src/generated/engine.dart 3413:17            AnalysisContextImpl._getDartResolutionData
  package:analyzer/src/generated/engine.dart 3442:14            AnalysisContextImpl._getDartResolutionData2
  package:analyzer/src/generated/engine.dart 1692:7             AnalysisContextImpl.computeLibraryElement
  package:source_gen/src/utils.dart 170:20                      _getLibraryElement
  package:source_gen/src/utils.dart 183:20                      _getLibraryElements.<fn>
  dart:core                                                     Iterable.toList
  package:source_gen/src/utils.dart 105:44                      getAnalysisContextForProjectPath.<async>
  ===== asynchronous gap ===========================
  dart:async                                                    _Completer.completeError
  package:source_gen/src/utils.dart 112:1                       getAnalysisContextForProjectPath.<async>
  ===== asynchronous gap ===========================
  dart:async                                                    Future.Future.microtask
  package:source_gen/src/utils.dart                             getAnalysisContextForProjectPath
  test/annotation_test.dart 242:23                              _getTestLibElement.<async>
  ===== asynchronous gap ===========================
  dart:async                                                    _Future.then
  test/annotation_test.dart 240:7                               _getTestLibElement.<async>
  ===== asynchronous gap ===========================
  dart:async                                                    Future.Future.microtask
  test/annotation_test.dart                                     _getTestLibElement
  test/annotation_test.dart 27:26                               main.<fn>.<async>
  ===== asynchronous gap ===========================
  dart:async                                                    Future.Future.microtask
  test/annotation_test.dart                                     main.<fn>

Support incremental generations

I like to be able to have a source like

@Value()
abstract class _MyClass {
  String a, b;
}

The generator for ValueClass whould generate:

@DefaultConstructor()
@EqualsAndHashCode()
@ToString()
@ToConcretPublicClass()
abstract class _MyClass {
  String a, b;
}

Then the generated part would be re-analyzed and the generator for DefaultConstructor would generate:

@EqualsAndHashCode()
@ToString()
@ToConcretPublicClass()
abstract class _MyClass {
  String a, b;
  _MyClass({this.a, this.b});
}

Next round with the generator for EqualsAndHashCode:

@ToString()
@ToConcretPublicClass()
abstract class _MyClass {
  String a, b;
  _MyClass({this.a, this.b});
  @override int get hashCode => hashObjects([a, b]);
  @override bool operator ==(o) => identical(this, o) || o.runtimeType == runtimeType && o.a == a && o.b == b;
}

Next round with the generator for ToString:

@ToConcretPublicClass()
abstract class _MyClass {
  String a, b;
  _MyClass({this.a, this.b});
  @override int get hashCode => hashObjects([a, b]);
  @override bool operator ==(o) => identical(this, o) || o.runtimeType == runtimeType && o.a == a && o.b == b;
  @override String toString() => "_MyClass(a=$a, b=$b)";
}

Next round with the generator for ToConcretPublicClass:

class MyClass {
  String a, b;
  MyClass({this.a, this.b});
  @override int get hashCode => hashObjects([a, b]);
  @override bool operator ==(o) => identical(this, o) || o.runtimeType == runtimeType && o.a == a && o.b == b;
  @override String toString() => "MyClass(a=$a, b=$b)";
}

This issue is a little related to #28

protect against DartFormatter crashes

Dart formatter crash on valid dart files causes source_gen to stop generating code. Please protect against this and log formatting problem instead

var formatter = new DartFormatter();
try {
genPartContent = formatter.format(genPartContent);
} catch ... log formatter crash.

Example crash:
Unhandled exception:
Uncaught Error: Class 'ForEachStatement' has no instance getter 'iterable'.

NoSuchMethodError: method not found: 'iterable'
Receiver: Instance of 'ForEachStatement'
Arguments: []
Stack Trace:
#0 Object._noSuchMethod (dart:core-patch/object_patch.dart:42)
#1 Object.noSuchMethod (dart:core-patch/object_patch.dart:45)
#2 SourceVisitor.visitForEachStatement (package:dart_style/src/source_visitor.dart:695:16)
#3 ForEachStatement.accept (package:analyzer/src/generated/ast.dart:7003:62)
#4 SourceVisitor.visit (package:dart_style/src/source_visitor.dart:1442:16)
#5 SourceVisitor.visitNodes (package:dart_style/src/source_visitor.dart:1515:10)
#6 SourceVisitor.visitBlock (package:dart_style/src/source_visitor.dart:301:15)

Provide way to exclude some generators from command line.

Here's my use case. I have 2 generators.

First is slow, downloads some files and then generates code which usually doesn't change.
Second is fast, I use it to generate unit-tests. It could be called several times in a minute.
Right now I have to comment code that calls first generator. But it will be very convenient to just pass some args to prevent first generator from being run.

strategy to work with sublime

Sublime doesn't use build.dart. Let's deliver something that gives our sublime friends an edit-gen cycle, without asking them to manually run build.dart.

strategy to work with WebStorm/IntelliJ

IntelliJ/WebStorm don't use build.dart. Let's deliver something that gives our sublime friends an edit-gen cycle, without asking them to manually run build.dart.

Option to generate an instance "updateFromJson" mixin method

Sometimes, instead of using the factory "fromJson" method that creates a new instance, it is preferable to be able to apply json for an existing object (use case: settings deserialization, when there are already listeners attached to the events of the existing settings instance). This option would be quite handy in such cases!

Ability to specify a directory(root) to place generated code

Some source control systems require generated code to be rooted at separate path.

Example:
/source-repo/dartproject/lib/sourcefile.dart
/generated/dartproject/lib/sourcefile.g.dart

Strawman :
build(args, const [const MyGenerator()],
librarySearchPaths: ['lib'] , repoPath: "/source-repo" , outputPath: "/generated");

Support property-based attributes

/// Annotation for view registration module.
class _ParticleModule {
  const _ParticleModule();
}
const _ParticleModule ParticleModule = const _ParticleModule();

JsonGenerator: DateTime check can fail

Minor bug: in the sample JSON generator the following expression is used to check for DateTime:

return type.element.library.isDartCore && type.name == 'DateTime';

That can fail if the examined type is dynamic, since in that case type.element.library is null.

To many parts make the build hang

With 1.9.0.dev_10_13 and source_gen-0.2.4 I face an issue when there are too many parts in a lib. The generator never ends.

I don't know if this is an analyzer issue but 0.24.0 doesn't fix this issue.

Here is a example to reproduce.

pubspec.yaml :

name: source_gen_issue
version: 0.0.1
dependencies:
  source_gen: any

Generate parts and lib by running genlib.dart :

import 'dart:io';

main() {
  var libContent = '''
library source_gen_issue;

import 'package:source_gen/generators/json_serializable.dart';

part 'my.g.dart';

''';
  for (var i = 1; i <= 80; i++) {
    var n = i.toString().padLeft(3, '0');
    libContent += "part 'part${n}.dart';\n";
    new File('lib/part${n}.dart').writeAsStringSync('''
part of source_gen_issue;

@JsonSerializable()
class Person${n} extends Object with _\$Person${n}SerializerMixin {
  final String firstName, middleName, lastName;
  final DateTime dateOfBirth;

  Person${n}(this.firstName, this.lastName, {this.middleName, this.dateOfBirth});

  factory Person${n}.fromJson(json) => _\$Person${n}FromJson(json);
}
''');
  }
  new File('lib/my.dart').writeAsStringSync(libContent);
}

build.dart :

import 'package:source_gen/generators/json_serializable_generator.dart' as json;
import 'package:source_gen/source_gen.dart';

void main(List<String> args) {
  build(args, const [const json.JsonSerializableGenerator(),],
      librarySearchPaths: ['lib']).then((msg) {
    print(msg);
  });
}

Either by running build.dart or with a change in one of the file the build.dart never ends. In my example the limit is between 70 and 80 parts.

Allowing generator for library

It seems that LibraryElement are not exposed to Generator.

class LibGenerator extends Generator {
  const LibGenerator();

  Future<String> generate(Element element) async {
    if (element is! LibraryElement) return null;
    // never here because LibraryElement is not exposed
    return "// I'm generated for lib";
  }
}

Generate libraries instead of parts?

Just curious about the motivation for choosing generated parts instead of libraries.

For hand-written code I generally prefer using export to part. It gets rid of all the ugly _s and allows one to import or export the sub-libraries elsewhere, such as tests. Could then either import or export (or both) the generated lib depending on use case.

Provide a way to follow symlinks when finding libs

In io.dart:

Future _populateFiles( Directory directory, StreamController<String> controller) async { return directory.list(recursive: false, followLinks: false).asyncMap((fse) {

I need a way to set "followLinks" to true, please.

Consider integrating a template engine

Writing code directly to a StringBuffer can be very tedious and error prone, so it would be nice if we could write templates for generated code.

For now, I'm using mustache templates to avoid the use of StringBuffer (I've rewritten the json serializable generator to test this out, and you can see the code here). However, it's more a hack than a proper solution, since mustache was built for generating html files, not source code.

It'd be really cool if we had something like StringTemplate for Dart and source_gen.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.