Шаблоны сайтов интернет магазинов
29 925 р.
39 900 р.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<!-- Critical Styles -->
<!-- Начало фрагмента №1 -->
<style>
html {
box-sizing: border-box;
}
*,
*:after,
*:before {
box-sizing: inherit;
}
body {
margin: 0;
padding: 0;
font: 18px 'Oxygen', Helvetica;
background: #ececec;
}
header {
height: 60px;
background: #512DA8;
color: #fff;
display: flex;
align-items: center;
padding: 0 40px;
box-shadow: 1px 2px 6px 0px #777;
}
h1 {
margin: 0;
}
.banner {
text-decoration: none;
color: #fff;
cursor: pointer;
}
main {
display: flex;
justify-content: center;
height: calc(100vh - 140px);
padding: 20px 40px;
overflow-y: auto;
}
button {
background: #512DA8;
border: 2px solid #512DA8;
cursor: pointer;
box-shadow: 1px 1px 3px 0px #777;
color: #fff;
padding: 10px 15px;
border-radius: 20px;
}
.button {
display: flex;
justify-content: center;
}
button:hover {
box-shadow: none;
}
footer {
height: 40px;
background: #2d3850;
color: #fff;
display: flex;
align-items: center;
padding: 40px;
}
</style>
<!-- Конец фрагмента №1 -->
<title>Vanilla Todos PWA</title>
</head>
<body>
<body>
<!-- Main Application Section -->
<!-- Начало фрагмента №2 -->
<header>
<h3><font color="#3AC1EF">▍<a class="banner"> Vanilla Todos PWA </a></font></h3>
</header>
<main id="app"></main>
<footer>
<span>© 2019 Anurag Majumdar - Vanilla Todos SPA</span>
</footer>
<!-- Конец фрагмента №2 -->
<!-- Critical Scripts -->
<!-- Начало фрагмента №3 -->
<script async src="<%= htmlWebpackPlugin.files.chunks.main.entry %>"></script>
<!-- Конец фрагмента №3 -->
<noscript>
This site uses JavaScript. Please enable JavaScript in your browser.
</noscript>
</body>
</body>
</html>
<!-- Начало фрагмента №1 -->
).main
с идентификатором app
(<main id="app"></main>
).async
позволяет не блокировать парсер во время загрузки скриптов.var staticAssetsCacheName = 'todo-assets-v3';
var dynamicCacheName = 'todo-dynamic-v3';
// Начало фрагмента №1
self.addEventListener('install', function (event) {
self.skipWaiting();
event.waitUntil(
caches.open(staticAssetsCacheName).then(function (cache) {
cache.addAll([
'/',
"chunks/todo.d41d8cd98f00b204e980.js","index.html","main.d41d8cd98f00b204e980.js"
]
);
}).catch((error) => {
console.log('Error caching static assets:', error);
})
);
});
// Конец фрагмента №1
// Начало фрагмента №2
self.addEventListener('activate', function (event) {
if (self.clients && clients.claim) {
clients.claim();
}
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.filter(function (cacheName) {
return (cacheName.startsWith('todo-')) && cacheName !== staticAssetsCacheName;
})
.map(function (cacheName) {
return caches.delete(cacheName);
})
).catch((error) => {
console.log('Some error occurred while removing existing cache:', error);
});
}).catch((error) => {
console.log('Some error occurred while removing existing cache:', error);
}));
});
// Конец фрагмента №2
// Начало фрагмента №3
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request)
.then((fetchResponse) => {
return cacheDynamicRequestData(dynamicCacheName, event.request.url, fetchResponse.clone());
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.log(error);
})
);
});
// Конец фрагмента №3
function cacheDynamicRequestData(dynamicCacheName, url, fetchResponse) {
return caches.open(dynamicCacheName)
.then((cache) => {
cache.put(url, fetchResponse.clone());
return fetchResponse;
}).catch((error) => {
console.log(error);
});
}
install
сервис-воркера помогает кэшировать статические ресурсы. Здесь можно поместить в кэш ресурсы «скелета» приложения (CSS, JavaScript, изображения, и так далее) для первого маршрута (в соответствии с наполнением «скелета»). Кроме того, можно загрузить и другие ресурсы приложения, сделав так, чтобы оно могло бы работать без подключения к интернету. Кэширование ресурсов, помимо кэширования «скелета», соответствует шагу Pre-cache паттерна PRPL.activate
выполняется очистка неиспользуемых кэшей.@babel/core
@babel/plugin-syntax-dynamic-import
@babel/preset-env
babel-core
babel-loader
babel-preset-env
.babelrc
, рассчитанного на использование с Webpack:{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
presets
этого файла используется для настройки Babel на транспиляцию ES6 в ES5, а строка plugins
— для того, чтобы в Webpack можно было бы пользоваться динамическим импортом.webpack.config.js
):module.exports = {
entry: {
// Входные файлы
},
output: {
// Выходные файлы
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
},
plugins: [
// Плагины
]
};
rules
этого файла описывается использование загрузчика babel-loader
для настройки процесса транспиляции. Остальные части этого файла, ради краткости, опущены.sass-loader
css-loader
style-loader
MiniCssExtractPlugin
module.exports = {
entry: {
// Входные файлы
},
output: {
// Выходные файлы
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.scss$/,
use: [
'style-loader',
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css'
}),
]
};
rules
. Так как мы используем плагин для извлечения CSS-стилей, в раздел plugins
вносится соответствующая запись.clean-webpack-plugin
: для очистки содержимого папки dist
.compression-webpack-plugin
: для сжатия содержимого папки dist
.copy-webpack-plugin
: для копирования статических ресурсов, например — файлов, из папок с исходными данными приложения в папку dist
.html-webpack-plugin
: для создания файла index.html
в папке dist
.webpack-md5-hash
: для хэширования файлов приложения в папке dist
.webpack-dev-server
: для запуска локального сервера, используемого в ходе разработки.webpack.config.js
:const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackMd5Hash = require('webpack-md5-hash');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = (env, argv) => ({
entry: {
main: './src/main.js'
},
devtool: argv.mode === 'production' ? false : 'source-map',
output: {
path: path.resolve(__dirname, 'dist'),
chunkFilename:
argv.mode === 'production'
? 'chunks/[name].[chunkhash].js'
: 'chunks/[name].js',
filename:
argv.mode === 'production' ? '[name].[chunkhash].js' : '[name].js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.scss$/,
use: [
'style-loader',
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin('dist', {}),
new MiniCssExtractPlugin({
filename:
argv.mode === 'production'
? '[name].[contenthash].css'
: '[name].css'
}),
new HtmlWebpackPlugin({
inject: false,
hash: true,
template: './index.html',
filename: 'index.html'
}),
new WebpackMd5Hash(),
new CopyWebpackPlugin([
// {
// from: './src/assets',
// to: './assets'
// },
// {
// from: 'manifest.json',
// to: 'manifest.json'
// }
]),
new CompressionPlugin({
algorithm: 'gzip'
})
],
devServer: {
contentBase: 'dist',
watchContentBase: true,
port: 1000
}
});
argv
, который представляет аргументы, передаваемые этой функции при выполнении команд webpack
или webpack-dev-server
. Вот как описание этих команд выглядит в файле проекта package.json
:"scripts": {
"build": "webpack --mode production && node build-sw",
"serve": "webpack-dev-server --mode=development --hot",
},
npm run build
, будет выполнена сборка продакшн-версии приложения. Если выполнить команду npm run serve
, будет запущен сервер разработки, поддерживающий процесс работы над приложением.plugins
и devServer
вышеприведённого файла показана настройка плагинов и сервера разработки.new CopyWebpackPlugin
, задают ресурсы, которые нужно скопировать из исходных материалов приложения.dist
и добавляет их в качестве содержимого кэша в шаблоне сервис-воркера. После этого файл сервис-воркера будет записан в папку dist
. Те концепции, о которых мы говорили в применении к сервис-воркерам, не меняются. Вот код скрипта build-sw.js
:const glob = require('glob');
const fs = require('fs');
const dest = 'dist/sw.js';
const staticAssetsCacheName = 'todo-assets-v1';
const dynamicCacheName = 'todo-dynamic-v1';
// Начало фрагмента №1
let staticAssetsCacheFiles = glob
.sync('dist/**/*')
.map((path) => {
return path.slice(5);
})
.filter((file) => {
if (/\.gz$/.test(file)) return false;
if (/sw\.js$/.test(file)) return false;
if (!/\.+/.test(file)) return false;
return true;
});
// Конец фрагмента №1
const stringFileCachesArray = JSON.stringify(staticAssetsCacheFiles);
// Начало фрагмента №2
const serviceWorkerScript = `var staticAssetsCacheName = '${staticAssetsCacheName}';
var dynamicCacheName = '${dynamicCacheName}';
self.addEventListener('install', function (event) {
self.skipWaiting();
event.waitUntil(
caches.open(staticAssetsCacheName).then(function (cache) {
cache.addAll([
'/',
${stringFileCachesArray.slice(1, stringFileCachesArray.length - 1)}
]
);
}).catch((error) => {
console.log('Error caching static assets:', error);
})
);
});
self.addEventListener('activate', function (event) {
if (self.clients && clients.claim) {
clients.claim();
}
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.filter(function (cacheName) {
return (cacheName.startsWith('todo-')) && cacheName !== staticAssetsCacheName;
})
.map(function (cacheName) {
return caches.delete(cacheName);
})
).catch((error) => {
console.log('Some error occurred while removing existing cache:', error);
});
}).catch((error) => {
console.log('Some error occurred while removing existing cache:', error);
}));
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request)
.then((fetchResponse) => {
return cacheDynamicRequestData(dynamicCacheName, event.request.url, fetchResponse.clone());
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.log(error);
})
);
});
function cacheDynamicRequestData(dynamicCacheName, url, fetchResponse) {
return caches.open(dynamicCacheName)
.then((cache) => {
cache.put(url, fetchResponse.clone());
return fetchResponse;
}).catch((error) => {
console.log(error);
});
}
`;
// Конец фрагмента №2
// Начало фрагмента №3
fs.writeFile(dest, serviceWorkerScript, function(error) {
if (error) return;
console.log('Service Worker Write success');
});
// Конец фрагмента №3
dist
помещается в массив staticAssetsCacheFiles
.dist
, которое может со временем меняться. Для этого используется константа stringFileCachesArray
.serviceWorkerScript
, записывается в файл, находящийся по адресу dist/sw.js
.node build-sw
. Её нужно выполнить после того, как будет завершено выполнение команды webpack --mode production
.package.json
нашего проекта, в котором можно найти сведения о пакетах, используемых в этом проекте:{
"name": "vanilla-todos-pwa",
"version": "1.0.0",
"description": "A simple todo application using ES6 and Webpack",
"main": "src/main.js",
"scripts": {
"build": "webpack --mode production && node build-sw",
"serve": "webpack-dev-server --mode=development --hot"
},
"keywords": [],
"author": "Anurag Majumdar",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/preset-env": "^7.2.3",
"autoprefixer": "^9.4.5",
"babel-core": "^6.26.3",
"babel-loader": "^8.0.4",
"babel-preset-env": "^1.7.0",
"clean-webpack-plugin": "^1.0.0",
"compression-webpack-plugin": "^2.0.0",
"copy-webpack-plugin": "^4.6.0",
"css-loader": "^2.1.0",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.11.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"terser": "^3.14.1",
"webpack": "^4.28.4",
"webpack-cli": "^3.2.1",
"webpack-dev-server": "^3.1.14",
"webpack-md5-hash": "0.0.6"
}
}
app.js
:import { appTemplate } from './app.template';
import { AppModel } from './app.model';
export const AppComponent = {
// Код компонента App...
};
app.js
, в котором можно видеть их применение.import { appTemplate } from './app.template';
import { AppModel } from './app.model';
export const AppComponent = {
init() {
this.appElement = document.querySelector('#app');
this.initEvents();
this.render();
},
initEvents() {
this.appElement.addEventListener('click', event => {
if (event.target.className === 'btn-todo') {
import( /* webpackChunkName: "todo" */ './todo/todo.module')
.then(lazyModule => {
lazyModule.TodoModule.init();
})
.catch(error => 'An error occurred while loading Module');
}
});
document.querySelector('.banner').addEventListener('click', event => {
event.preventDefault();
this.render();
});
},
render() {
this.appElement.innerHTML = appTemplate(AppModel);
}
};
AppComponent
, которым сразу же можно пользоваться в других частях приложения.chunks
, не были бы кэшированы.AppComponent
, в частности, там, где настраивается обработчик события щелчка по кнопке (речь идёт о методе объекта initEvents()
). А именно, если пользователь приложения щёлкнет по кнопке btn-todo
, будет загружен модуль todo.module
. Этот модуль представляет собой обычный JavaScript-файл, который содержит набор компонентов, представленных в виде объектов.this
в таких функциях указывало бы на контекст, в котором объявлена функция. Помимо этого стрелочные функции позволяют писать код, который оказывается компактнее, чем при использовании обычных функций. Вот пример такой функции:export const appTemplate = model => `
<section class="app">
<h3><font color="#3AC1EF">▍ ${model.title} </font></h3>
<section class="button">
<button class="btn-todo"> Todo Module </button>
</section>
</section>
`;
appTemplate
принимает модель (параметр model
) и возвращает HTML-строку, содержащую данные, взятые из модели. Формирование строки выполняется с использованием технологии шаблонных литералов. Ими удобно пользоваться для представления многострочных конструкций, в которые нужно добавлять какие-либо данные.reduce()
для сборки HTML-строк:const WorkModel = [
{
id: 1,
src: '',
alt: '',
designation: '',
period: '',
description: ''
},
{
id: 2,
src: '',
alt: '',
designation: '',
period: '',
description: ''
},
//...
];
const workCardTemplate = (cardModel) => `
<section id="${cardModel.id}" class="work-card">
<section class="work__image">
<img class="work__image-content" type="image/svg+xml" src="${
cardModel.src
}" alt="${cardModel.alt}" />
</section>
<section class="work__designation">${cardModel.designation}</section>
<section class="work__period">${cardModel.period}</section>
<section class="work__content">
<section class="work__content-text">
${cardModel.description}
</section>
</section>
</section>
`;
export const workTemplate = (model) => `
<section class="work__section">
<section class="work-text">
<header class="header-text">
<span class="work-text__header"> Work </span>
</header>
<section class="work-text__content content-text">
<p class="work-text__content-para">
This area signifies work experience
</p>
</section>
</section>
<section class="work-cards">
${model.reduce((html, card) => html + workCardTemplate(card), '')}
</section>
</section>
`;
model
. Это — пример массива с данными модели, который можно обработать с помощью метода reduce()
для того чтобы реализовать шаблон, подходящий для повторного использования.model.reduce
, которая помогает формировать HTML-строку, содержащую множество компонентов, подходящих для повторного использования. Эта функция принимает, в качестве первого аргумента, аккумулятор, а в качестве второго — значения массива.