Replace Mailchimp's 140KB JS

This commit is contained in:
Daniel Vassallo
2019-09-30 16:58:39 -07:00
parent 0f90cdc44b
commit 60f5afd76e
19 changed files with 9475 additions and 1681 deletions

10
.babelrc Executable file
View File

@@ -0,0 +1,10 @@
{
"presets": [
"@babel/preset-env"
],
"plugins": [
[
"@babel/transform-runtime"
]
]
}

2
.gitignore vendored
View File

@@ -1,5 +1,3 @@
node_modules/ node_modules/
dist/ dist/
.DS_Store .DS_Store
public/style.css

10415
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,16 +2,42 @@
"name": "homepage", "name": "homepage",
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"clean": "rm -Rf ./dist && rm -f ./public/style.css", "clean": "rm -Rf dist",
"build": "npm run clean && postcss ./tailwind.style.css -o ./dist/style.css && cp -R ./public/* ./dist/ && cp ./dist/style.css ./public/style.css" "build": "npm run clean && webpack --mode production --config webpack.config.js",
"start": "webpack-dev-server --mode development --config webpack.config.js"
}, },
"author": "Daniel Vassallo", "author": "Daniel Vassallo",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.5",
"@babel/node": "^7.4.5",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.5",
"@fullhuman/postcss-purgecss": "^1.3.0", "@fullhuman/postcss-purgecss": "^1.3.0",
"autoprefixer": "^9.6.1", "autoprefixer": "^9.6.0",
"postcss-cli": "^6.1.3", "babel-loader": "^8.0.6",
"tailwind": "^4.0.0", "babel-polyfill": "^6.26.0",
"tailwindcss": "^1.1.2" "css-loader": "^2.1.1",
"favicons-webpack-plugin": "^1.0.2",
"file-loader": "^3.0.1",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.8.0",
"nodemon": "^1.19.1",
"npm-run-all": "^4.1.5",
"open": "6.0.0",
"opn-browser-webpack-plugin": "0.0.7",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"postcss-loader": "^3.0.0",
"purgecss-webpack-plugin": "^1.6.0",
"source-map-loader": "^0.2.4",
"style-loader": "^0.23.1",
"tailwindcss": "^1.0.4",
"terser-webpack-plugin": "^1.3.0",
"url-loader": "^1.1.2",
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.5.1"
} }
} }

View File

@@ -1,14 +1,6 @@
const purgecss = require('@fullhuman/postcss-purgecss')({
content: [
'./public/**/*.html'
],
defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
})
module.exports = { module.exports = {
plugins: [ plugins: [
require('tailwindcss'), require('tailwindcss'),
require('autoprefixer'), require('autoprefixer')
purgecss
] ]
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1 +0,0 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

File diff suppressed because one or more lines are too long

1
src/footer.html Normal file
View File

@@ -0,0 +1 @@
<div class="font-sans tracking-tight my-10 text-xs">Copyright © 2019 Encrypted Development LLC</div>

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -2,17 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>Userbase</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Userbase</title>
<link rel="stylesheet" href="style.css">
<link rel="apple-touch-icon" sizes="180x180" href="img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="img/favicon-16x16.png">
<link rel="manifest" href="img/site.webmanifest">
</head> </head>
<body class="pb-10"> <body class="pb-10">
@@ -22,8 +14,7 @@
<img class="w-32 h-32" src="./img/icon.png"> <img class="w-32 h-32" src="./img/icon.png">
</div> </div>
<h1 class="font-black text-6xl leading-none tracking-tight font-logo">Userbase</h1> <h1 class="font-black text-6xl leading-none tracking-tight font-logo">Userbase</h1>
<h2 class="text-2xl leading-tight mt-1 tracking-tight">You won't believe it's not a <h2 class="text-2xl leading-tight mt-1 tracking-tight">You won't believe it's not a database!</h2>
database!</h2>
</div> </div>
<hr> <hr>
@@ -31,8 +22,8 @@
<div class="my-10"> <div class="my-10">
<h3 class="font-black text-3xl tracking-tight" id="what-is-userbase">What is Userbase?</h3> <h3 class="font-black text-3xl tracking-tight" id="what-is-userbase">What is Userbase?</h3>
<div class="text-left font-sans tracking-tight"> <div class="text-left font-sans tracking-tight">
<p class="mb-4">Userbase is like a database, but purpose-built for web app user data. It's accessible <p class="mb-4">Userbase is like a database, but purpose-built for web app user data. It's accessible directly
directly from the browser through a very simple JavaScript SDK.</p> from the browser through a very simple JavaScript SDK.</p>
</div> </div>
</div> </div>
@@ -41,22 +32,19 @@
<div class="my-10"> <div class="my-10">
<h3 class="font-black text-3xl tracking-tight" id="why-not-a-database">Why isn't it a database?</h3> <h3 class="font-black text-3xl tracking-tight" id="why-not-a-database">Why isn't it a database?</h3>
<div class="text-left font-sans tracking-tight"> <div class="text-left font-sans tracking-tight">
<p class="mb-4">With Userbase you can durably store, update, delete, and query user data. But that's where <p class="mb-4">With Userbase you can durably store, update, delete, and query user data. But that's where the
the similarities with traditional databases end.</p> similarities with traditional databases end.</p>
<h4 class="font-bold text-xl tracking-tight" id="zero-management">Zero management</h4> <h4 class="font-bold text-xl tracking-tight" id="zero-management">Zero management</h4>
<p class="mb-4">Unlike a real database, all Userbase <p class="mb-4">Unlike a real database, all Userbase queries run in the browser, with the server-side acting as
queries run in the browser, with the server-side acting as a dumb data store. There's no database to manage a dumb data store. There's no database to manage and worry about.
and worry about.
</p> </p>
<h4 class="font-bold text-xl tracking-tight" id="user-management">Built-in user management</h4> <h4 class="font-bold text-xl tracking-tight" id="user-management">Built-in user management</h4>
<p class="mb-4">Unlike a real database, Userbase takes care of your user accounts. It comes with built-in <p class="mb-4">Unlike a real database, Userbase takes care of your user accounts. It comes with built-in APIs
APIs for user signups, logins, and access control.</p> for user signups, logins, and access control.</p>
<h4 class="font-bold text-xl tracking-tight" id="e2ee">End-to-end encrypted</h4> <h4 class="font-bold text-xl tracking-tight" id="e2ee">End-to-end encrypted</h4>
<p class="mb-4">Unlike a real database, Userbase won't show you what your users store in your web <p class="mb-4">Unlike a real database, Userbase won't show you what your users store in your web app.</p>
app.</p>
<p class="mb-4"></p>Wait, what!? — Yes, that's a feature — maybe the most important feature. Userbase spares you <p class="mb-4"></p>Wait, what!? — Yes, that's a feature — maybe the most important feature. Userbase spares you
from from the liability of handling user data by encrypting everything in the browser, using keys that always stay
the liability of handling user data by encrypting everything in the browser, using keys that always stay
with the user.</p> with the user.</p>
</div> </div>
</div> </div>
@@ -88,8 +76,8 @@
<h4 class="font-bold text-xl tracking-tight" id="oss">Open source</h4> <h4 class="font-bold text-xl tracking-tight" id="oss">Open source</h4>
<p class="mb-4">Userbase is being developed in the open, and is <a <p class="mb-4">Userbase is being developed in the open, and is <a
href="https://github.com/encrypted-dev/userbase" target="_blank" rel="noopener noreferrer">100% href="https://github.com/encrypted-dev/userbase" target="_blank" rel="noopener noreferrer">100%
open-source</a>, MIT licensed. You can modify open-source</a>, MIT licensed. You can modify and extend the backend to your liking, and run it yourself in
and extend the backend to your liking, and run it yourself in your AWS account, always under your control.</p> your AWS account, always under your control.</p>
<h4 class="font-bold text-xl tracking-tight" id="saas">As a service</h4> <h4 class="font-bold text-xl tracking-tight" id="saas">As a service</h4>
<p class="mb-4">Or for just $39/month you can go fully serverless, backendless, databaseless, and AWSless!</p> <p class="mb-4">Or for just $39/month you can go fully serverless, backendless, databaseless, and AWSless!</p>
@@ -105,32 +93,24 @@
<p class="mb-4">Enter your email to receive occasional updates about Userbase. No spam ever.</p> <p class="mb-4">Enter your email to receive occasional updates about Userbase. No spam ever.</p>
</div> </div>
<div id="mc_embed_signup"> <form
<form action="https://danielvassallo.us20.list-manage.com/subscribe/post?u=e3b0fd293a0e6d7ea0080fafe&amp;id=d0aae9dd3e"
action="https://danielvassallo.us20.list-manage.com/subscribe/post?u=e3b0fd293a0e6d7ea0080fafe&amp;id=d0aae9dd3e" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate"
method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank">
target="_blank" novalidate> <div class="mc-field-group">
<div id="mc_embed_signup_scroll"> <input type="email" value="" name="EMAIL" id="mce-EMAIL" required autocorrect="off" spellcheck="false"
<div class="mc-field-group"> placeholder="Email address"
<input type="email" value="" name="EMAIL" id="mce-EMAIL" placeholder="Email address" class="font-light text-sm h-10 p-2 border border-gray-500 outline-none w-64 rounded font-mono my-4 inline-block">
class="font-light text-sm h-10 p-2 border border-gray-500 outline-none w-64 rounded font-mono my-4 inline-block">
<input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe"
class="rounded-lg w-40 h-10 bg-yellowish font-bold cursor-pointer text-gray-900 inline-block relative"
style="top: 1px;">
</div>
<div id="mce-responses" class="clear">
<div class="response" id="mce-error-response" style="display:none"></div>
<div class="response" id="mce-success-response" style="display:none"></div>
</div>
<div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text"
name="b_e3b0fd293a0e6d7ea0080fafe_d0aae9dd3e" tabindex="-1" value=""></div>
<div class="clear"></div>
</div>
</form>
</div>
<!--End mc_embed_signup-->
<input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe"
class="rounded-lg w-40 h-10 bg-yellowish font-bold cursor-pointer text-gray-900 inline-block relative"
style="top: 1px;">
</div>
<div class="mc-status" class="hidden"></div>
<div style="position: absolute; left: -5000px;" aria-hidden="true">
<input type="text" name="b_e3b0fd293a0e6d7ea0080fafe_d0aae9dd3e" tabindex="-1" value=""></div>
<div class="clear"></div>
</form>
</div> </div>
@@ -141,10 +121,8 @@
<div class="text-left font-sans tracking-tight"> <div class="text-left font-sans tracking-tight">
<p class="mb-4">This product is the work of <a href="https://twitter.com/dvassallo" target="_blank" <p class="mb-4">This product is the work of <a href="https://twitter.com/dvassallo" target="_blank"
rel="noopener noreferrer">Daniel Vassallo</a> and <a href="https://twitter.com/justinberman95" rel="noopener noreferrer">Daniel Vassallo</a> and <a href="https://twitter.com/justinberman95"
target="_blank" rel="noopener noreferrer">Justin Berman</a>. target="_blank" rel="noopener noreferrer">Justin Berman</a>. We're a small independent business, structured
We're a small independent business, structured to be lean, profitable, and sustainable. We're here for the to be lean, profitable, and sustainable. We're here for the long haul.
long
haul.
</p> </p>
<p class="mb-4">If you have any questions, or there's anything we can do to help you with your web app, please <p class="mb-4">If you have any questions, or there's anything we can do to help you with your web app, please
@@ -154,16 +132,10 @@
<hr> <hr>
<div class="font-sans tracking-tight my-10 text-xs"> ${require('./footer.html')}
Copyright © 2019 Encrypted Development LLC
</div>
</div> </div>
<script type='text/javascript' src='mc-validate.js'></script>
<script type='text/javascript'>
(function ($) { window.fnames = new Array(); window.ftypes = new Array(); fnames[0] = 'EMAIL'; ftypes[0] = 'email'; fnames[1] = 'FNAME'; ftypes[1] = 'text'; fnames[2] = 'LNAME'; ftypes[2] = 'text'; fnames[3] = 'ADDRESS'; ftypes[3] = 'address'; fnames[4] = 'PHONE'; ftypes[4] = 'phone'; fnames[5] = 'BIRTHDAY'; ftypes[5] = 'birthday'; }(jQuery)); var $mcj = jQuery.noConflict(true);
</script>
</body> </body>
</html> </html>

67
src/index.js Normal file
View File

@@ -0,0 +1,67 @@
import './style.css'
var serialize = function (form) {
var serialized = ''
for (var i = 0; i < form.elements.length; i++) {
var field = form.elements[i]
if (!field.name || field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') continue
if ((field.type !== 'checkbox' && field.type !== 'radio') || field.checked) {
serialized += '&' + encodeURIComponent(field.name) + "=" + encodeURIComponent(field.value)
}
}
return serialized;
}
window.displayMailChimpStatus = function (data) {
if (!data.result || !data.msg) return
var mcStatus = document.querySelector('.mc-status')
if (!mcStatus) return
mcStatus.innerHTML = data.msg
//mcStatus.addAttribute('tabindex', '-1')
mcStatus.focus()
mcStatus.classList.remove('hidden')
if (data.result === 'error') {
mcStatus.classList.remove('mc-success')
mcStatus.classList.add('mc-error')
return;
}
mcStatus.classList.remove('mc-error');
mcStatus.classList.add('mc-success')
}
var submitMailChimpForm = function (form) {
var url = form.getAttribute('action')
url = url.replace('/post?u=', '/post-json?u=')
url += serialize(form) + '&c=displayMailChimpStatus'
var script = window.document.createElement('script')
script.src = url
var ref = window.document.getElementsByTagName('script')[0]
ref.parentNode.insertBefore(script, ref)
script.onload = function () {
this.remove()
}
}
document.addEventListener('submit', function (event) {
if (!event.target.classList.contains('validate')) return
event.preventDefault()
submitMailChimpForm(event.target)
}, false)

View File

@@ -2,8 +2,6 @@
@tailwind components; @tailwind components;
@tailwind utilities;
a { a {
@apply underline; @apply underline;
} }
@@ -11,3 +9,17 @@ a {
a:hover { a:hover {
@apply text-orange-700; @apply text-orange-700;
} }
.mc-status:focus {
@apply outline-none
}
.mc-success {
@apply text-green-600;
}
.mc-error {
@apply text-red-600;
}
@tailwind utilities;

124
webpack.config.js Executable file
View File

@@ -0,0 +1,124 @@
const path = require('path')
const glob = require('glob')
const webpack = require('webpack')
const TerserPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgecssPlugin = require('purgecss-webpack-plugin')
const HtmlWebPackPlugin = require('html-webpack-plugin')
const FaviconsWebpackPlugin = require('favicons-webpack-plugin')
const OpenBrowserPlugin = require('opn-browser-webpack-plugin')
module.exports = (env, argv) => {
const PATHS = {
src: path.join(__dirname, 'src')
}
const config = {
entry: {
main: './src/index.js'
},
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: '[name].js',
globalObject: 'this'
},
target: 'web',
devtool: 'source-map',
resolve: {
extensions: ['.js', '.jsx'],
},
performance: {
hints: false
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader',
]
},
{
enforce: 'pre',
test: /\.js$/,
use: ['source-map-loader'],
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
options: {
interpolate: true,
minimize: false
}
}
]
},
{
test: /\.(woff|woff2|eot|ttf|otf|png|svg|jpg|gif)$/,
use: ['file-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'style.css',
chunkFilename: 'style.css',
ignoreOrder: false
}),
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
new HtmlWebPackPlugin({
template: './src/index.html',
filename: './index.html'
}),
new FaviconsWebpackPlugin('./src/img/icon.png'),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.WatchIgnorePlugin(['./dist'])
]
}
if (argv.mode == 'development') {
config.devtool = 'inline-source-map'
config.devServer = {
watchContentBase: true,
historyApiFallback: true,
hot: true,
inline: true,
host: '0.0.0.0',
port: 3000
}
config.plugins.push(new webpack.HotModuleReplacementPlugin())
config.plugins.push(new OpenBrowserPlugin({
url: 'http://localhost:3000'
}))
}
if (argv.mode == 'production') {
config.optimization = {
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true
}),
new OptimizeCSSAssetsPlugin({})
]
}
}
return config
}