Giter VIP home page Giter VIP logo

publicsuffix-go's Introduction

Public Suffix for Go

The package publicsuffix provides a Go domain name parser based on the Public Suffix List.

Tests GoDoc

Currently, publicsuffix-go requires Go version 1.9 or greater. We do our best not to break older versions of Go if we don't have to, but due to tooling constraints, we don't always test older versions.

Getting started

Clone the repository in your workspace and move into it:

mkdir -p $GOPATH/src/github.com/weppos && cd $_
git clone [email protected]:weppos/publicsuffix-go.git
cd publicsuffix-go

Fetch the dependencies:

go get ./...

Run the test suite.

go test ./...

Testing

The following command runs the entire test suite.

go test ./...

There are 3 different test suites built into this library:

  • Acceptance: the acceptance test suite contains some high level tests to ensure the library behaves as expected
  • PSL: the PSL test suite runs the library against the official Public Suffix test cases
  • Unit: the unit test suite stresses the various single components of this package

Installation

go get github.com/weppos/publicsuffix-go

Usage

This is a simple example that demonstrates how to use the package with the default options and the default Public Suffix list packaged with the library.

package main

import (
    "fmt"

    "github.com/weppos/publicsuffix-go/publicsuffix"
)

func main() {
    // Extract the domain from a string
    // using the default list
    fmt.Println(publicsuffix.Domain("example.com"))             // example.com
    fmt.Println(publicsuffix.Domain("www.example.com"))         // example.com
    fmt.Println(publicsuffix.Domain("example.co.uk"))           // example.co.uk
    fmt.Println(publicsuffix.Domain("www.example.co.uk"))       // example.co.uk

    // Parse the domain from a string
    // using the default list
    fmt.Println(publicsuffix.Parse("example.com"))             // &DomainName{"com", "example", ""}
    fmt.Println(publicsuffix.Parse("www.example.com"))         // &DomainName{"com", "example", "www"}
    fmt.Println(publicsuffix.Parse("example.co.uk"))           // &DomainName{"co.uk", "example", ""}
    fmt.Println(publicsuffix.Parse("www.example.co.uk"))       // &DomainName{"co.uk", "example", "www"}
}

Ignoring Private Domains

The PSL is composed by two list of suffixes: IANA suffixes, and Private Domains.

Private domains are submitted by private organizations. By default, private domains are not ignored. Sometimes, you want to ignore these domains and only query against the IANA suffixes. You have two options:

  1. Ignore the domains at runtime
  2. Create a custom list without the private domains

In the first case, the private domains are ignored at runtime: they will still be included in the lists but the lookup will skip them when found.

publicsuffix.DomainFromListWithOptions(publicsuffix.DefaultList(), "google.blogspot.com", nil)
// google.blogspot.com

publicsuffix.DomainFromListWithOptions(publicsuffix.DefaultList(), "google.blogspot.com", &publicsuffix.FindOptions{IgnorePrivate: true})
// blogspot.com

// Note that the DefaultFindOptions includes the private domains by default
publicsuffix.DomainFromListWithOptions(publicsuffix.DefaultList(), "google.blogspot.com", publicsuffix.DefaultFindOptions)
// google.blogspot.com

This solution is easy, but slower. If you find yourself ignoring the private domains in all cases (or in most cases), you may want to create a custom list without the private domains.

list := NewListFromFile("path/to/list.txt", &publicsuffix.ParserOption{PrivateDomains: false})
publicsuffix.DomainFromListWithOptions(list, "google.blogspot.com", nil)
// blogspot.com

IDN domains, A-labels and U-labels

A-label and U-label are two different ways to represent IDN domain names. These two encodings are also known as ASCII (A-label) or Pynucode vs Unicode (U-label). Conversions between U-labels and A-labels are performed according to the "Punycode" specification, adding or removing the ACE prefix as needed.

IDNA-aware applications generally use the A-label form for storing and manipulating data, whereas the U-labels can appear in presentation and user interface forms.

Although the PSL list has been traditionally U-label encoded, this library follows the common industry standards and stores the rules in their A-label form. Therefore, unless explicitly mentioned, any method call, comparison or internal representation is expected to be ASCII-compatible encoded (ACE).

Passing Unicode names to the library may either result in error or unexpected behaviors.

If you are interested in the details of this decision, you can read the full discussion here.

Differences with golang.org/x/net/publicsuffix

The golang.org/x/net/publicsuffix is a package part of the Golang x/net package, that provides a public suffix list implementation.

The main difference is that the x/net package is optimized for speed, but it's less flexible. The list is compiled and embedded into the package itself. However, this is also the main downside. The list is not frequently refreshed, hence the results may be inaccurate, in particular if you heavily rely on the private domain section of the list. Changes in the IANA section are less frequent, whereas changes in the Private Domains section happens weekly.

This package provides the following extra features:

  • Ability to load an arbitrary list at runtime (e.g. you can feed your own list, or create multiple lists)
  • Ability to create multiple lists
  • Ability to parse a domain using a previously defined list
  • Ability to add custom rules to an existing list, or merge/load rules from other lists (provided as file or string)
  • Advanced access to the list rules
  • Ability to ignore private domains at runtime, or when the list is parsed

This package also aims for 100% compatibility with the x/net package. A special adapter is provided as a drop-in replacement. Simply change the include statement from

import (
    "golang.org/x/net/publicsuffix"
)

to

import (
    "github.com/weppos/publicsuffix-go/net/publicsuffix"
)

The github.com/weppos/publicsuffix-go/net/publicsuffix package defines the same methods defined in golang.org/x/net/publicsuffix, but these methods are implemented using the github.com/weppos/publicsuffix-go/publicsuffix package.

Note that the adapter doesn't offer the flexibility of github.com/weppos/publicsuffix-go/publicsuffix, such as the ability to use multiple lists or disable private domains at runtime.

cookiejar.PublicSuffixList interface

This package implements the cookiejar.PublicSuffixList interface. It means it can be used as a value for the PublicSuffixList option when creating a net/http/cookiejar.

import (
    "net/http/cookiejar"
    "github.com/weppos/publicsuffix-go/publicsuffix"
)

deliciousJar := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.CookieJarList})

License

Copyright (c) 2016-2024 Simone Carletti. This is Free Software distributed under the MIT license.

publicsuffix-go's People

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

publicsuffix-go's Issues

Fail to extract eTLD from s3-us-west-1.amazonaws.com

Hi,

Stumbled into some kind of wierd issue.

This fails
publicsuffix.EffectiveTLDPlusOne("s3-us-west-1.amazonaws.com")

This doesn't fail
publicsuffix.EffectiveTLDPlusOne("s1-us-west-1.amazonaws.com")

This doesn't fail
publicsuffix.EffectiveTLDPlusOne("s3-us-west-1.google.com")

This doesn't fail
publicsuffix.EffectiveTLDPlusOne("s3-us-hello-1.amazonaws.com")

I thought I had found a workaround by doing
publicsuffix.EffectiveTLDPlusOne("https://s3-us-west-1.amazonaws.com")

But then this doesn't work
publicsuffix.EffectiveTLDPlusOne("https://google.com")

Question on the PSL update

Hello! First of all, thanks for this package! Impressive work!

QQ: I've realized that there is an automation to update the PSL that runs quite often, but tags containing such changes are not frequently created. Is the recommendation for projects using this one to depend on the main branch to have an updated view of the PSL instead of depending on tags?

Domain validation

Hello, is it possible to add domain validation for such cases?

dom, err := publicsuffix.Domain("adsbygoogle || []).push({});")
fmt.Println(dom, err)

Make defaultListVersion public

I want to display the version of the PSL in my project as part of --version flag but right now I have to create cookie jar list object and call string on it, which I think is unnecessary. The constant should be simply made public.

Incompatibility with latest golang.org/x/net/idna version

Commit golang/net@67957fd introduced a few non-backward compatibility changes.

Given the commit message says:

The API extends the old API but is intended to be backwards
compatible. The old test file serves as a test for this purpose.

those changes are probably bugs.

As a result, the library currently doesn't pass the tests if you use the latest idna version (after 67957fd0b1868d2b9f4fd2b3a3474c83bc3d49c3).

➜  publicsuffix-go git:(master) ✗ cd ~/go/src/golang.org/x/net/idna; git co 69d4b8; cd -;
Previous HEAD position was 67957fd... idna: use code generated by internal x/text package
HEAD is now at 69d4b8a... http2: remove unnecessary TODO for trailer keys allocation
~/go/src/github.com/weppos/publicsuffix-go

➜  publicsuffix-go git:(master) ✗ go test ./...
?   	github.com/weppos/publicsuffix-go	[no test files]
?   	github.com/weppos/publicsuffix-go/cmd/gen	[no test files]
?   	github.com/weppos/publicsuffix-go/cmd/load	[no test files]
ok  	github.com/weppos/publicsuffix-go/net/publicsuffix	0.023s
ok  	github.com/weppos/publicsuffix-go/publicsuffix	0.060s

➜  publicsuffix-go git:(master) ✗ cd ~/go/src/golang.org/x/net/idna; git co 67957f; cd -;
Previous HEAD position was 69d4b8a... http2: remove unnecessary TODO for trailer keys allocation
HEAD is now at 67957fd... idna: use code generated by internal x/text package
~/go/src/github.com/weppos/publicsuffix-go

➜  publicsuffix-go git:(master) ✗ go test ./...
?   	github.com/weppos/publicsuffix-go	[no test files]
?   	github.com/weppos/publicsuffix-go/cmd/gen	[no test files]
?   	github.com/weppos/publicsuffix-go/cmd/load	[no test files]
ok  	github.com/weppos/publicsuffix-go/net/publicsuffix	0.037s
--- FAIL: TestPsl (0.02s)
	psl_test.go:72: PSL(.example.com) should have returned error, got: example.com
	psl_test.go:72: PSL(.example.example) should have returned error, got: example.example
--- FAIL: TestListFind (0.00s)
	publicsuffix_test.go:247: Find(example.uk) = &{2  2 false}, want &{2 uk 2 false}
	publicsuffix_test.go:247: Find(example.co.uk) = &{2  2 false}, want &{2 uk 2 false}
	publicsuffix_test.go:247: Find(foo.example.co.uk) = &{2  2 false}, want &{2 uk 2 false}
	publicsuffix_test.go:247: Find(british-library.uk) = &{2  2 false}, want &{3 british-library.uk 2 false}
	publicsuffix_test.go:247: Find(foo.british-library.uk) = &{2  2 false}, want &{3 british-library.uk 2 false}
	publicsuffix_test.go:247: Find(blogspot.com) = &{1 com 1 false}, want &{1 blogspot.com 2 true}
	publicsuffix_test.go:247: Find(foo.blogspot.com) = &{1 com 1 false}, want &{1 blogspot.com 2 true}
FAIL
FAIL	github.com/weppos/publicsuffix-go/publicsuffix	0.051s

Here's a simple test that reproduces one of the bugs:

➜  publicsuffix-go git:(master) ✗ cd ~/go/src/golang.org/x/net/idna; git co 69d4b8; cd -; go run idna-test.go
Note: checking out '69d4b8'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 69d4b8a... http2: remove unnecessary TODO for trailer keys allocation
~/go/src/github.com/weppos/publicsuffix-go
Empty string works!
Result: ``

➜  publicsuffix-go git:(master) ✗ cd ~/go/src/golang.org/x/net/idna; git co 67957f; cd -; go run idna-test.go
Previous HEAD position was 69d4b8a... http2: remove unnecessary TODO for trailer keys allocation
HEAD is now at 67957fd... idna: use code generated by internal x/text package
~/go/src/github.com/weppos/publicsuffix-go
Error converting empty string: idna: invalid label ""
exit status 1
package main

import (
	"fmt"
	"os"

	"golang.org/x/net/idna"
)

func main() {
	s, err := idna.ToASCII("")
	if err != nil {
		fmt.Printf("Error converting empty string: %v\n", err)
		os.Exit(1)
	}

	fmt.Println("Empty string works!")
	fmt.Printf("Result: `%v`\n", s)
}

But empty strings are not the only issue. Even * seems to be causing conversion errors:

diff --git a/publicsuffix/publicsuffix.go b/publicsuffix/publicsuffix.go
index 0e2167a..a5e46a1 100644
--- a/publicsuffix/publicsuffix.go
+++ b/publicsuffix/publicsuffix.go
@@ -13,6 +13,7 @@ import (
        "strings"

        "golang.org/x/net/idna"
+       "log"
 )

 const (
@@ -176,6 +177,11 @@ Scanning:
                                rule, err = NewRule(line)
                        } else {
                                rule, err = NewRuleUnicode(line)
+                               if err == nil {
+                                       log.Printf("Evaluating `%v`, converted into `%v`", line, rule.Value)
+                               } else {
+                                       log.Printf("Evaluating `%v`, error: %v", line, err)
+                               }
                        }
                        if err != nil {
                                return []Rule{}, err
➜  publicsuffix-go git:(master) ✗ go test ./... --run 'TestListFind'
?   	github.com/weppos/publicsuffix-go	[no test files]
?   	github.com/weppos/publicsuffix-go/cmd/gen	[no test files]
?   	github.com/weppos/publicsuffix-go/cmd/load	[no test files]
ok  	github.com/weppos/publicsuffix-go/net/publicsuffix	0.035s
2017/01/08 12:45:49 Evaluating `com`, converted into `com`
2017/01/08 12:45:49 Evaluating `*.uk`, error: idna: disallowed rune U+002E
--- FAIL: TestListFind (0.00s)
	publicsuffix_test.go:247: Find(example.uk) = &{2  2 false}, want &{2 uk 2 false}
	publicsuffix_test.go:247: Find(example.co.uk) = &{2  2 false}, want &{2 uk 2 false}
	publicsuffix_test.go:247: Find(foo.example.co.uk) = &{2  2 false}, want &{2 uk 2 false}
	publicsuffix_test.go:247: Find(british-library.uk) = &{2  2 false}, want &{3 british-library.uk 2 false}
	publicsuffix_test.go:247: Find(foo.british-library.uk) = &{2  2 false}, want &{3 british-library.uk 2 false}
	publicsuffix_test.go:247: Find(blogspot.com) = &{1 com 1 false}, want &{1 blogspot.com 2 true}
	publicsuffix_test.go:247: Find(foo.blogspot.com) = &{1 com 1 false}, want &{1 blogspot.com 2 true}
FAIL
FAIL	github.com/weppos/publicsuffix-go/publicsuffix	0.018s

Find implementation exposes default rules definition

Because Find returns pointers, it allows the user of the library to modify default definitions. It's true that it shouldn't be done to begin with, but it would be good to protect against it.

// Find and returns the most appropriate rule for the domain name.
func (l *List) Find(name string, options *FindOptions) *Rule {
	if options == nil {
		options = DefaultFindOptions
	}

	part := name
	for {
		rule, ok := l.rules[part]

		if ok && rule.Match(name) && !(options.IgnorePrivate && rule.Private) {
			return rule
		}

		i := strings.IndexRune(part, '.')
		if i < 0 {
			return options.DefaultRule
		}

		part = part[i+1:]
	}

	return nil
}

We can rewrite it as such:

// Find and returns the most appropriate rule for the domain name.
func (l *List) Find(name string, options *FindOptions) *Rule {
	if options == nil {
		options = DefaultFindOptions
	}

	part := name
	var out Rule
	for {
		rule, ok := l.rules[part]

		if ok && rule.Match(name) && !(options.IgnorePrivate && rule.Private) {
			out = *rule
			return &out
		}

		i := strings.IndexRune(part, '.')
		if i < 0 {
			out = *options.DefaultRule
			return &out
		}

		part = part[i+1:]
	}

	return nil
}

DomainFromListWithOptions return error while it shall not

Run below code, it shall have no error, however, it throws error saying "gov.uk" is a suffix

package main

import (
"fmt"

"github.com/weppos/publicsuffix-go/publicsuffix"

)

func main() {

var publicSuffixFindOptions = &publicsuffix.FindOptions{IgnorePrivate: true, DefaultRule: publicsuffix.DefaultRule}
// using the default list
str, err := publicsuffix.DomainFromListWithOptions(publicsuffix.DefaultList, "gov.uk", publicSuffixFindOptions)
if err == nil {
	fmt.Println(str)
} else {
	fmt.Println("erro happened %s", err)
}

}

Calling AddRule can modify DefaultList

It would have been nice if DefaultList's type was List instead of *List, but I realize that changing it now would break backwards compatibility. I wanted to add a few of my own custom domains to the list but I realized that I would be modifying DefaultList's rules by doing so. My work around is to create my own global variable like so:


var (
	defaultList = new(publicsuffix.List)

	customSuffices = map[string]bool{
		// my list here
	}
)

func init() {
	*defaultList = *publicsuffix.DefaultList
	for customSuffix := range customSuffices {
		defaultList.AddRule(publicsuffix.MustNewRule(customSuffix))
	}
}

But I think it's easy to overlook for other users of the library. Maybe the best solution is to create a NewDefaultList() function?

Accept A-labels

In letsencrypt/boulder#2278 we have an issue where TLDs that are IDNs are not recognized by Boulder as ending in a public suffix. Boulder currently calls publicsuffix.DefaultList.Find with the A-label form of the domain.

We have a few options:

  1. Boulder could convert to U-label for querying publicsuffix
  2. publicsuffix-go could switch to looking up by A-label by default
  3. publicsuffix-go could offer a different set of methods for looking up by A-label.

I think (2) is my ideal solution. What do you think?

0e.vc different result if list is manually loaded

When I do a "Domain" on www.example.0e.vc (0e.vc is in the PSL, so it should act like co.uk), I get a different result if I use the baked-in list vs loading the list myself.

package main

import (
	"fmt"

	"github.com/weppos/publicsuffix-go/publicsuffix"
)

const publicSuffixFile = "/opt/datapulse/data/effective_tld_names.dat"
const publicSuffixURL = "https://publicsuffix.org/list/effective_tld_names.dat"

func main() {
	// Extract the domain from a string
	// using the default list
	fmt.Println(publicsuffix.Domain("www.example.co.uk")) // example.co.uk
	fmt.Println(publicsuffix.Domain("www.example.0e.vc")) // example.0e.vc

	list, _ := publicsuffix.NewListFromFile(publicSuffixFile, &publicsuffix.ParserOption{PrivateDomains: false})
	fmt.Println(publicsuffix.DomainFromListWithOptions(list, "www.example.co.uk", nil)) // example.co.uk
	fmt.Println(publicsuffix.DomainFromListWithOptions(list, "www.example.0e.vc", nil)) // example.0e.vc
}

Actual results:

[kwhite@hornet genpslnod]$ psltest
example.co.uk <nil>
example.0e.vc <nil>
example.co.uk <nil>
0e.vc <nil>

Notice the final row. It should be example.0e.vc

0e.vc is in the list I'm using:

[kwhite@hornet genpslnod]$ grep 0e\.vc /opt/datapulse/data/effective_tld_names.dat
0e.vc

(That list was retrieved by code a few hours ago. It is just the file from the URL in the code saved to disk.)

`.za` not recognized as an IANA country code

Hi there, 👋

It's possible I'm missing something obvious but it looks like .za is not being recognized as an IANA country code.

Here's my test program:

package main

import (
	"fmt"

	"github.com/weppos/publicsuffix-go/publicsuffix"
)

func main() {
	opts := &publicsuffix.FindOptions{
		IgnorePrivate: true,
		DefaultRule:   nil,
	}
	for _, d := range []string{"foo.ca", "foo.za"} {
		rule := publicsuffix.DefaultList.Find(d, opts)
		fmt.Printf("%s -> %#v\n", d, rule)
	}
}

And the resulting output:

foo.ca -> &publicsuffix.Rule{Type:1, Value:"ca", Length:1, Private:false}
foo.za -> (*publicsuffix.Rule)(nil)

Based on the IANA root db entry for .za:

.za | country-code | ZA Domain Name Authority

I would have expected a non-nil return from the call to Find given the FindOptions provided.

Is this a bug or a misunderstanding on my part?

Thanks!

Domains with consecutive dots (empty labels)

https://publicsuffix.org/list/

A domain or rule can be split into a list of labels using the separator "." (dot). The separator is not part of any of the labels. Empty labels are not permitted, meaning that leading and trailing dots are ignored.

Parse currently doesn't accept .google.com (leading empty label) or google.com. (trailing empty label) but accepts google..com (interior empty label)

publicsuffix.Parse("google..com")

Output:
TLD = "com"
SLD = ""
TRD = "google"

Is this normal?

Performance needs some work

According to this crude micro-benchmark on 12 made up domains, github.com/weppos/publicsuffix-go performs 460x slower than golang.org/x/net/publicsuffix:

Program:

package main

import (
	"fmt"
	"time"

	wlib "github.com/weppos/publicsuffix-go/publicsuffix"
	xlib "golang.org/x/net/publicsuffix"
)

var testCases = []string{
	"example.com",
	"example.id.au",
	"www.ck",
	"foo.bar.xn--55qx5d.cn",
	"a.b.c.minami.fukuoka.jp",
	"posts-and-telecommunications.museum",
	"www.example.pvt.k12.ma.us",
	"many.lol",
	"the.russian.for.moscow.is.xn--80adxhks",
	"blah.blah.s3-us-west-1.amazonaws.com",
	"thing.dyndns.org",
	"nosuchtld",
}

var wants = map[string]string{
	"example.com":                            "example.com",
	"example.id.au":                          "example.id.au",
	"www.ck":                                 "www.ck",
	"foo.bar.xn--55qx5d.cn":                  "bar.xn--55qx5d.cn",
	"a.b.c.minami.fukuoka.jp":                "c.minami.fukuoka.jp",
	"posts-and-telecommunications.museum":    "",
	"www.example.pvt.k12.ma.us":              "example.pvt.k12.ma.us",
	"many.lol":                               "many.lol",
	"the.russian.for.moscow.is.xn--80adxhks": "is.xn--80adxhks",
	"blah.blah.s3-us-west-1.amazonaws.com":   "blah.s3-us-west-1.amazonaws.com",
	"thing.dyndns.org":                       "thing.dyndns.org",
	"nosuchtld":                              "",
}

func main() {
	do(false)
	do(true)
}

func do(xNetLibrary bool) {
	libraryName := "github.com/weppos/publicsuffix-go"
	if xNetLibrary {
		libraryName = "golang.org/x/net/publicsuffix"
	}

	now := time.Now()
	for i := 0; i < 100; i++ {
		for _, tc := range testCases {
			got := ""
			if xNetLibrary {
				got, _ = xlib.EffectiveTLDPlusOne(tc)
			} else {
				got, _ = wlib.Domain(tc)
			}

			if i != 0 {
				continue
			}
			if want := wants[tc]; got != want {
				panic(fmt.Sprintf("xNetLibrary=%t, tc=%q: got %q, want %q",
					xNetLibrary, tc, got, want))
			}
		}
	}
	since := time.Since(now)

	fmt.Printf("%16d nanos  %s\n", since, libraryName)
}

Output:

       172856110 nanos  github.com/weppos/publicsuffix-go
          375676 nanos  golang.org/x/net/publicsuffix

Parsing domain from URL?

I might be using this lib incorrectly, but the example

fmt.Println(publicsuffix.Parse("www.example.co.uk"))

is displaying simply "www.example.co.uk " under Go 1.7.3 for me.

Is there an approach to extract just "example" from the above?

Doesn't remove www

dom, err := publicsuffix.Domain("www.mastercard.ck")
fmt.Println(dom)
fmt.Println(err)

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.