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/
dist/
.DS_Store
public/style.css

10409
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,16 +2,42 @@
"name": "homepage",
"version": "1.0.0",
"scripts": {
"clean": "rm -Rf ./dist && rm -f ./public/style.css",
"build": "npm run clean && postcss ./tailwind.style.css -o ./dist/style.css && cp -R ./public/* ./dist/ && cp ./dist/style.css ./public/style.css"
"clean": "rm -Rf dist",
"build": "npm run clean && webpack --mode production --config webpack.config.js",
"start": "webpack-dev-server --mode development --config webpack.config.js"
},
"author": "Daniel Vassallo",
"license": "MIT",
"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",
"autoprefixer": "^9.6.1",
"postcss-cli": "^6.1.3",
"tailwind": "^4.0.0",
"tailwindcss": "^1.1.2"
"autoprefixer": "^9.6.0",
"babel-loader": "^8.0.6",
"babel-polyfill": "^6.26.0",
"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 = {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
purgecss
require('autoprefixer')
]
}

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">
<head>
<title>Userbase</title>
<meta charset="utf-8">
<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>
<body class="pb-10">
@@ -22,8 +14,7 @@
<img class="w-32 h-32" src="./img/icon.png">
</div>
<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
database!</h2>
<h2 class="text-2xl leading-tight mt-1 tracking-tight">You won't believe it's not a database!</h2>
</div>
<hr>
@@ -31,8 +22,8 @@
<div class="my-10">
<h3 class="font-black text-3xl tracking-tight" id="what-is-userbase">What is Userbase?</h3>
<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
directly from the browser through a very simple JavaScript SDK.</p>
<p class="mb-4">Userbase is like a database, but purpose-built for web app user data. It's accessible directly
from the browser through a very simple JavaScript SDK.</p>
</div>
</div>
@@ -41,22 +32,19 @@
<div class="my-10">
<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">
<p class="mb-4">With Userbase you can durably store, update, delete, and query user data. But that's where
the similarities with traditional databases end.</p>
<p class="mb-4">With Userbase you can durably store, update, delete, and query user data. But that's where the
similarities with traditional databases end.</p>
<h4 class="font-bold text-xl tracking-tight" id="zero-management">Zero management</h4>
<p class="mb-4">Unlike a real database, all Userbase
queries run in the browser, with the server-side acting as a dumb data store. There's no database to manage
and worry about.
<p class="mb-4">Unlike a real database, all Userbase queries run in the browser, with the server-side acting as
a dumb data store. There's no database to manage and worry about.
</p>
<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
APIs for user signups, logins, and access control.</p>
<p class="mb-4">Unlike a real database, Userbase takes care of your user accounts. It comes with built-in APIs
for user signups, logins, and access control.</p>
<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
app.</p>
<p class="mb-4">Unlike a real database, Userbase won't show you what your users store in your web app.</p>
<p class="mb-4"></p>Wait, what!? — Yes, that's a feature — maybe the most important feature. Userbase spares you
from
the liability of handling user data by encrypting everything in the browser, using keys that always stay
from the liability of handling user data by encrypting everything in the browser, using keys that always stay
with the user.</p>
</div>
</div>
@@ -88,8 +76,8 @@
<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
href="https://github.com/encrypted-dev/userbase" target="_blank" rel="noopener noreferrer">100%
open-source</a>, MIT licensed. You can modify
and extend the backend to your liking, and run it yourself in your AWS account, always under your control.</p>
open-source</a>, MIT licensed. You can modify and extend the backend to your liking, and run it yourself in
your AWS account, always under your control.</p>
<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>
@@ -105,32 +93,24 @@
<p class="mb-4">Enter your email to receive occasional updates about Userbase. No spam ever.</p>
</div>
<div id="mc_embed_signup">
<form
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"
target="_blank" novalidate>
<div id="mc_embed_signup_scroll">
target="_blank">
<div class="mc-field-group">
<input type="email" value="" name="EMAIL" id="mce-EMAIL" placeholder="Email address"
<input type="email" value="" name="EMAIL" id="mce-EMAIL" required autocorrect="off" spellcheck="false"
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">
<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="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>
</div>
</form>
</div>
<!--End mc_embed_signup-->
</div>
@@ -141,10 +121,8 @@
<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"
rel="noopener noreferrer">Daniel Vassallo</a> and <a href="https://twitter.com/justinberman95"
target="_blank" rel="noopener noreferrer">Justin Berman</a>.
We're a small independent business, structured to be lean, profitable, and sustainable. We're here for the
long
haul.
target="_blank" rel="noopener noreferrer">Justin Berman</a>. We're a small independent business, structured
to be lean, profitable, and sustainable. We're here for the long haul.
</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
@@ -154,16 +132,10 @@
<hr>
<div class="font-sans tracking-tight my-10 text-xs">
Copyright © 2019 Encrypted Development LLC
</div>
${require('./footer.html')}
</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>
</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 utilities;
a {
@apply underline;
}
@@ -11,3 +9,17 @@ a {
a:hover {
@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
}