Giter VIP home page Giter VIP logo

bundle-wizard's Introduction

πŸ§™β€β™‚οΈ bundle-wizard

npm version

This command line utility makes it simple to create visualizations of the JS bundles that were fetched for any specific page (or "entry point") of a web app.

Example

npx bundle-wizard reddit.com

bundle-wizard interaction showing code for reddit

Check out a live demo of this visualization

Try it out on a production app:

Try any of the following commands to take a peek at the JavaScript code different sites are shipping:

  1. npx bundle-wizard reddit.com
  2. npx bundle-wizard codesandbox.io
  3. npx bundle-wizard gatsbyjs.org
  4. npx bundle-wizard codecademy.com
  5. npx bundle-wizard id.atlassian.com

Try it out on an app running locally:

Want to use bundle-wizard but haven't deployed your app yet? It's as easy as:

1. Build your app locally

e.g. npm run build

2. Serve the build folder

e.g. npx serve -s build

3. Call bundle-wizard with the correct localhost url

e.g. npx bundle-wizard localhost:5000/sign-up

Additional features

View source code

If sourcemaps are properly configured you should be able to click on a square to see the code it represents:

demonstration of code feature

(Note: for performance reasons, this functionality will be automatically stripped if you decide to package your bundle-wizard files in order to host or share them).

Filter bundles with regex

Want to know all the bundles that contain code from certain library (say, momentjs or lodash)? Wondering how much weight in your bundles is from node_modules vs custom code? You can answer questions like these by putting a search string or regex into the bottom search bar that allows you to filter the view based on the name of the containing folder bundle name, or script:

demo of simple search

Optional command line arguments

url (initial argument)

To skip the initial prompt, provide a url as a first argument:

npx bundle-wizard reddit.com

interact flag

If you need to do some work in the browser getting the page ready for analysis (perhaps by signing in and then visiting a certain page), use the following command:

npx -p puppeteer -p bundle-wizard bundle-wizard --interact

The persistent npm equivalent would be running:

npm install -g puppeteer bundle-wizard

bundle-wizard --interact

You might be wondering why you have to install puppeteer as a peer dependency to use the interact command. By default bundle-wizard uses puppeteer-core, which is faster to download than puppeteer because it doesn't come bundled with a version of chromium. Since the --interact command opens a browser in non-headless mode, unlike the default bundle-wizard command, it requires the full puppeteer package to work reliably.

After running this command and specifying a url, you will see a browser window that will pop up that you can interact with. When you are ready to proceed, type y into the console in respond to the waiting prompt to reload the page and start measuring performance.

Note: While this tool does not record any data, it's still recommended from a common sense perspective to enter login information only for test accounts.

desktop flag

By default, bundle-wizard will analyze a mobile version of the site. To analyze the desktop version instead, pass the --desktop flag:

npx bundle-wizard --desktop

ignoreHTTPSErrors flag

If you are running an HTTPS connection on localhost and want to test a local site, you'll need to use this setting to prevent self-signed certificate errors:

npx bundle-wizard https://localhost:5000 --ignoreHTTPSErrors

port argument

By default, bundle-wizard will try to find an open port in the 3000 range. However, if you'd like it to run on a certain port, you can do so by passing in a value for --port:

npx bundle-wizard https://localhost:3000 --port=4000

How it works

bundle-wizard uses Puppeteer to download a web page, measure performance, and examine the JavaScript it sends to the client. It then analyzes the code using the awesome source-map-explorer library and creates a custom visualization.

Requirement: downloadable sourcemaps

This utility downloads sourcemaps from the url you provide. This requires the sourcemaps to be publically available, or at least available on your network. You might need to point to a testing instead of production build, for instance, as some apps disable sourcemaps in production.

Don't have access to sourcemaps in your prod app? Try building your app locally.

bundle-wizard's People

Contributors

8holon avatar aholachek avatar dependabot[bot] avatar ricokahler 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

bundle-wizard's Issues

bundle-wizard is great – thank you

Hey, just wanted to drop by and say thanks for creating bundle-wizard.

I’ve just recently learned about bundle-wizard and have used it a couple times, and it’s so great and convenient for analyzing sites. The attention to detail is incredible: I love how different modules are highlighted differently (makes it much easier to parse bundle contents than when looking at all-white source-map-explorer); and how clicking a concrete module shows its content (with syntax highlighting!). The captured β€œlong task” warnings are also πŸ’―.

So, thanks a lot for making this! bundle-wizard is just great.

What does "coverage" mean?

The report summary provides the size and coverage of bundles. However, I don't know what coverage means and the significance of it. Where can I find resources to learn about it?

[feat] export a visualization that works without a server

Hi!
Thank you for the option to save a visualization of a test, it helps sharing and having an history of a project to further compare.

The current exported index.html file can not be simply dragged and dropped to a browser, we have to serve it from an http server in order to see anything. Like webpack-bundle-analyzer, it would be easier to not need a server at all.
There is paths problems that can be corrected manually from the exported HTML :

- <script src="/client.a5432830.js">
+ <script src="./client.a5432830.js">

but after this there is more serious issues, the first one coming from Fetching a local file
client.a5432830.js:1080 Fetch API cannot load file:///…/dist/treeData.json. URL scheme must be "http" or "https" for CORS request.

The way webpack-bundle-analyzer does it is to put all JS, JSON and even images directly into the HTML, saving the full visualization into one file.
Would it be possible to do it this way ?

Potential Bugs and Anti-Patterns in the Codebase

Description

I ran DeepSource Static Code Analysis upon the Project, the results for which are available here.

The Static Code Analysis Tool found potential bugs and anti-patterns in the Code, that can be detrimental at a later point of time with respect to the Project. DeepSource helps you to automatically find and fix issues in your code during code reviews. This tool looks for anti-patterns, bug risks, performance problems, and raises issues.

Some of the notable issues are:

  • Missing Key Prop here
  • Unused Variables here
  • Empty Block Statements here

There are plenty of other issues in relation to Bug Discovery and Anti-Patterns which you would be interested to take a look at.

Does not support M1 Macs/Arm64

When trying to run via npx this fails silently.

Upon trying to globally install I am faced with this error

❯ npm i -g bundle-wizard puppeteer
npm WARN deprecated [email protected]: this library is no longer supported
npm WARN deprecated [email protected]: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated [email protected]: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated [email protected]: Version no longer supported. Upgrade to @latest
npm ERR! code 1
npm ERR! path /opt/homebrew/lib/node_modules/bundle-wizard/node_modules/puppeteer
npm ERR! command failed
npm ERR! command sh -c node install.js
npm ERR! The chromium binary is not available for arm64.
npm ERR! If you are on Ubuntu, you can install with:
npm ERR!
npm ERR!  sudo apt install chromium
npm ERR!
npm ERR!
npm ERR!  sudo apt install chromium-browser
npm ERR!
npm ERR! /opt/homebrew/lib/node_modules/bundle-wizard/node_modules/puppeteer/lib/cjs/puppeteer/node/BrowserFetcher.js:115
npm ERR!                     throw new Error();
npm ERR!                     ^
npm ERR!
npm ERR! Error
npm ERR!     at /opt/homebrew/lib/node_modules/bundle-wizard/node_modules/puppeteer/lib/cjs/puppeteer/node/BrowserFetcher.js:115:27
npm ERR!     at FSReqCallback.oncomplete (node:fs:198:21)

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/davidaghassi/.npm/_logs/2022-07-13T17_26_14_300Z-debug-0.log

Most likely puppeteer needs updating to support the newer version of chromium that has arm support

Chrome could not be killed

πŸ“‹  Writing coverage file to disk...
Error: Chrome could not be killed Command failed: taskkill /pid 19896 /T /F
ERROR: The process "19896" not found.

    at C:\Users\Seonglae\AppData\Local\npm-cache\_npx\bb7f27a7b68f073a\node_modules\chrome-launcher\dist\chrome-launcher.js:282:28
    at new Promise (<anonymous>)
    at Launcher.kill (C:\Users\Seonglae\AppData\Local\npm-cache\_npx\bb7f27a7b68f073a\node_modules\chrome-launcher\dist\chrome-launcher.js:262:16)
    at Object.<anonymous> (C:\Users\Seonglae\AppData\Local\npm-cache\_npx\bb7f27a7b68f073a\node_modules\chrome-launcher\dist\chrome-launcher.js:54:29)
    at Generator.next (<anonymous>)
    at C:\Users\Seonglae\AppData\Local\npm-cache\_npx\bb7f27a7b68f073a\node_modules\chrome-launcher\dist\chrome-launcher.js:13:71
    at new Promise (<anonymous>)
    at __awaiter (C:\Users\Seonglae\AppData\Local\npm-cache\_npx\bb7f27a7b68f073a\node_modules\chrome-launcher\dist\chrome-launcher.js:9:12)
    at Object.kill (C:\Users\Seonglae\AppData\Local\npm-cache\_npx\bb7f27a7b68f073a\node_modules\chrome-launcher\dist\chrome-launcher.js:49:28)
    at downloadCoverage (C:\Users\Seonglae\AppData\Local\npm-cache\_npx\bb7f27a7b68f073a\node_modules\bundle-wizard\src\functions\downloadCoverage.js:163:30)     

⬇️   Downloading sourcemaps...
⚠️  No sourcemaps could be downloaded, analysis cannot proceed.

npx bundle-wizard google.com - this happens

Error: connect ECONNREFUSED ::1:1197

Hey! About a few seconds after executing the command npx bundle-wizard reddit.com, I receive the following error message:

PS C:\Windows\System32> npx bundle-wizard reddit.com

πŸ§™β€  Welcome to bundle-wizard

πŸ€–  Loading https://reddit.com ...


⚠️  Unable to fetch website data

Error: connect ECONNREFUSED ::1:1197
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1300:16) {
  errno: -4078,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '::1',
  port: 1197
}

Am I doing something wrong? I'm getting the same result with all other URLs.

PS C:\Windows\System32> node -v
v18.8.0

UnhandledPromiseRejectionWarning: Error

System:

  • ubuntu 18.04.1 LTS
  • npm 6.14.4
  • node 10.19.0
  • bundle-wizard 1.2.0

Hi. Thanks for developing this great tool. I am getting these errors while running bundle-wizard and not sure why / how to fix it:
image

(node:18250) UnhandledPromiseRejectionWarning: Error: Protocol error (Network.getResponseBody): No data found for resource with given identifier
    at Promise (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/lib/Connection.js:156:63)
    at new Promise (<anonymous>)
    at CDPSession.send (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/lib/Connection.js:155:16)
    at _contentPromise._bodyLoadedPromise.then (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/lib/HTTPResponse.js:58:53)
    at process._tickCallback (internal/process/next_tick.js:68:7)
  -- ASYNC --
    at HTTPResponse.<anonymous> (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/lib/helper.js:116:19)
    at page.on.response (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/bundle-wizard/src/functions/downloadCoverage.js:108:14)
    at /home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/node_modules/mitt/dist/mitt.js:1:233
    at Array.map (<anonymous>)
    at Object.emit (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/node_modules/mitt/dist/mitt.js:1:217)
    at Page.emit (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/lib/EventEmitter.js:72:22)
    at Page.networkManager.on (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/lib/Page.js:180:84)
    at /home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/node_modules/mitt/dist/mitt.js:1:233
    at Array.map (<anonymous>)
    at Object.emit (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/node_modules/mitt/dist/mitt.js:1:217)
(node:18250) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:18250) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:18250) UnhandledPromiseRejectionWarning: Error: Protocol error (Network.getResponseBody): No data found for resource with given identifier
    at Promise (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/lib/Connection.js:156:63)
    at new Promise (<anonymous>)
    at CDPSession.send (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/lib/Connection.js:155:16)
    at _contentPromise._bodyLoadedPromise.then (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/lib/HTTPResponse.js:58:53)
    at process._tickCallback (internal/process/next_tick.js:68:7)
  -- ASYNC --
    at HTTPResponse.<anonymous> (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/lib/helper.js:116:19)
    at page.on.response (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/bundle-wizard/src/functions/downloadCoverage.js:108:14)
    at /home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/node_modules/mitt/dist/mitt.js:1:233
    at Array.map (<anonymous>)
    at Object.emit (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/node_modules/mitt/dist/mitt.js:1:217)
    at Page.emit (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/lib/EventEmitter.js:72:22)
    at Page.networkManager.on (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/lib/Page.js:180:84)
    at /home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/node_modules/mitt/dist/mitt.js:1:233
    at Array.map (<anonymous>)
    at Object.emit (/home/thibauld/.nvm/versions/node/v10.19.0/lib/node_modules/puppeteer/node_modules/mitt/dist/mitt.js:1:217)
(node:18250) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)

Cannot read property 'emulate' of undefined

TypeError: Cannot read property 'emulate' of undefined
at downloadCoverage (/Users/sibelius/.npm/_npx/4891/lib/node_modules/bundle-wizard/src/functions/downloadCoverage.js:93:16)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async main (/Users/sibelius/.npm/_npx/4891/lib/node_modules/bundle-wizard/src/index.js:29:28)

Authentication Support

My application is behind authentication - on initial load the user will get redirected to login through a separate site.
If I'm reading the docs right that would prevent this tool from working on my app.
Support for this scenario to handle the login, then analyze the site after an authenticated load would be great.

[feat] Override some default puppeteer option

Hi !

first, thanks for this great tool !

I'm on a local site that is a bit slow and would like to wait a bit more, because I'm currently blocked by this

πŸ§™β€  Welcome to bundle-wizard
βœ” Which site would you like to analyze? Β· http://192.168.0.6:3000/courses-en-ligne
πŸ€–  Loading http://192.168.0.6:3000/courses-en-ligne ...
⚠️  Unable to fetch website data

TimeoutError: Navigation timeout of 30000 ms exceeded
    at /Users/jpv/.npm/_npx/25331/lib/node_modules/puppeteer/lib/cjs/puppeteer/common/LifecycleWatcher.js:106:111 {
  name: 'TimeoutError'
}

I guess puppeteer is the source of my trouble here, I would like to have it wait longer.
You already added a useful --ignoreHTTPSerror flag, maybe adding a way to specifically configure the timeout would be good.
I guess more demands could arrive around puppeteer itself, so maybe giving a proxy option could help prevent this :)

Thanks

Failed to generate source map visualization

$ npx bundle-wizard app.laserhub.com

Produces:

πŸ§™β€  Welcome to bundle-wizard

πŸ€–  Loading https://app.laserhub.com ...

🐒  Finishing up loading...

πŸ“‹  Writing coverage file to disk...

⬇️   Downloading sourcemaps...

πŸ–ΌοΈ   Generating visualization...

⚠️  Failed to generate source map visualization
{
  bundles: [],
  errors: [
    {
      bundleName: '/Users/max/.npm/_npx/14980/lib/node_modules/bundle-wizard/temp/downloads/4558822.js',
      code: 'Unknown',
      message: '"version" is a required argument.',
      error: Error: "version" is a required argument.
          at Object.getArg (/Users/max/.npm/_npx/14980/lib/node_modules/bundle-wizard/node_modules/source-map/lib/util.js:24:11)
          at /Users/max/.npm/_npx/14980/lib/node_modules/bundle-wizard/node_modules/source-map/lib/source-map-consumer.js:207:28
          at async loadSourceMap (/Users/max/.npm/_npx/14980/lib/node_modules/bundle-wizard/node_modules/source-map-explorer/dist/explore.js:45:16)
          at async Object.exploreBundle (/Users/max/.npm/_npx/14980/lib/node_modules/bundle-wizard/node_modules/source-map-explorer/dist/explore.js:29:25)
          at async Promise.all (index 0)
          at async explore (/Users/max/.npm/_npx/14980/lib/node_modules/bundle-wizard/node_modules/source-map-explorer/dist/api.js:31:19)
          at async visualizeBundles (/Users/max/.npm/_npx/14980/lib/node_modules/bundle-wizard/src/functions/visualizeBundles.js:21:18)
    }
  ]
}

node -v -> v14.4.0
Mac OS Catalina: 10.15.6

Add possibility to disable certificate verification

Hello ! πŸ‘‹

I found your tool very useful but when I wanted to try it on a development website on my machine I was greeted the error I pasted at the end of the issue.

Thank you in advance (:

Terminal log
❯ npx bundle-wizard

πŸ§™β€  Welcome to bundle-wizard

βœ” Which site would you like to analyze? Β· https://localhost:4200


πŸ€–  Recording page load info for https://localhost:4200 ...


❌  Unable to fetch website data

Error: net::ERR_CERT_INVALID at https://localhost:4200
    at navigate (/home/matthieu/.npm/_npx/16021/lib/node_modules/bundle-wizard/node_modules/puppeteer-core/lib/FrameManager.js:120:37)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async FrameManager.navigateFrame (/home/matthieu/.npm/_npx/16021/lib/node_modules/bundle-wizard/node_modules/puppeteer-core/lib/FrameManager.js:94:17)
    at async Frame.goto (/home/matthieu/.npm/_npx/16021/lib/node_modules/bundle-wizard/node_modules/puppeteer-core/lib/FrameManager.js:406:12)
    at async Page.goto (/home/matthieu/.npm/_npx/16021/lib/node_modules/bundle-wizard/node_modules/puppeteer-core/lib/Page.js:672:12)
    at async downloadCoverage (/home/matthieu/.npm/_npx/16021/lib/node_modules/bundle-wizard/src/download-coverage.js:106:3)
    at async main (/home/matthieu/.npm/_npx/16021/lib/node_modules/bundle-wizard/src/index.js:27:28)
  -- ASYNC --
    at Frame.<anonymous> (/home/matthieu/.npm/_npx/16021/lib/node_modules/bundle-wizard/node_modules/puppeteer-core/lib/helper.js:111:15)
    at Page.goto (/home/matthieu/.npm/_npx/16021/lib/node_modules/bundle-wizard/node_modules/puppeteer-core/lib/Page.js:672:49)
    at Page.<anonymous> (/home/matthieu/.npm/_npx/16021/lib/node_modules/bundle-wizard/node_modules/puppeteer-core/lib/helper.js:112:23)
    at downloadCoverage (/home/matthieu/.npm/_npx/16021/lib/node_modules/bundle-wizard/src/download-coverage.js:106:14)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async main (/home/matthieu/.npm/_npx/16021/lib/node_modules/bundle-wizard/src/index.js:27:28)

"No valid exports main found"

Any idea about this? Not sure what the root of the issue is / how to debug.

 ~ ξ‚° npx bundle-wizard https://mitchinson.dev
No valid exports main found for '/Users/bmitchinson/.npm/_npx/2631/lib/node_modules/bundle-wizard/node_modules/uuid'
 ~ ξ‚° npx bundle-wizard mitchinson.dev
No valid exports main found for '/Users/bmitchinson/.npm/_npx/2806/lib/node_modules/bundle-wizard/node_modules/uuid'

image

[Feature Request] Ability to specify port

Would you be open to accepting PRs to specify the port manually? (e.g. --port) I don't think get-port is resolving correctly on my machine.

I'll start a next.js server on port 3000 and bundle-wizard starts on port 3000 too (i didn't know that was possible!)

btw, love the project!

[Feature Request] Flag to generate report only

I tested this out and really liked the utility. I noticed while the local server was running I was asked if i wanted to dump the report (yes or no) interactively.

I was wondering if it would be possible to run this utility with a flag to simply "run and dump"?

bundle-wizard --no-open <url>, bundle-wizard --report-only <url> or something. Use-case is to simply run the tool primarily to generate the distributable content, perhaps even on CI. Requiring interactivity prevents this so far as I can tell.

Thanks for the tool!

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.