Тъй като уеб приложенията стават все по-сложни, превръщането на вашето уеб приложение в мащабиращо се от изключителна важност. Докато в миналото писането на ad-hoc JavaScript и jQuery би било достатъчно, в наши дни изграждането на уеб приложение изисква много по-голяма степен на дисциплина и официални практики за разработване на софтуер, като например:
Мрежата предоставя и някои свои уникални предизвикателства за развитие. Например, тъй като уеб страниците правят много асинхронни заявки, производителността на вашето уеб приложение може значително да се влоши от необходимостта да изисква стотици JS и CSS файлове, всеки със свои собствени малки режийни (заглавки, ръкостискания и т.н.). Този конкретен проблем често може да бъде разрешен чрез групиране на файловете, така че заявявате само един пакет JS и CSS файл, а не стотици отделни.
Също така е доста често да се използват езикови препроцесори като SASS и JSX, които се компилират в собствени JS и CSS, както и JS транпилатори като Babel, за да се възползват от ES6 кода, като същевременно се поддържа ES5 съвместимост.
Това се равнява на значителен брой задачи, които нямат нищо общо с писането на логиката на самото уеб приложение. Тук влизат бегачите на задачи. Целта на бегач на задачи е да автоматизира всички тези задачи, така че да можете да се възползвате от подобрена среда за разработка, като същевременно се фокусирате върху писането на приложението си. След като бегачът на задачите е конфигуриран, всичко, което трябва да направите, е да извикате една команда в терминал.
Ще използвам Глътка като бегач на задачи, защото е много удобен за разработчици, лесен за научаване и лесно разбираем.
API на Gulp се състои от четири функции:
какво мога да направя с програмирането на c
gulp.src
gulp.dest
gulp.task
gulp.watch
Ето например примерна задача, която използва три от тези четири функции:
gulp.task('my-first-task', function() { gulp.src('/public/js/**/*.js') .pipe(concat()) .pipe(minify()) .pipe(gulp.dest('build')) });
Когато my-first-task
се изпълнява, всички файлове, съответстващи на глобус модел /public/js/**/*.js
се минифицират и след това се прехвърлят в build
папка.
Красотата на това е в .pipe()
верига. Вземете набор от входни файлове, прекарайте ги през поредица от трансформации, след което върнете изходните файлове. За да направят нещата още по-удобни, действителните трансформации на тръбопроводи, като minify()
, често се извършват от NPM библиотеки. В резултат на това е много рядко на практика, че трябва да напишете свои собствени трансформации, освен да преименувате файлове в тръбата.
Следващата стъпка за разбиране на Gulp е разбирането на масива от зависимости на задачите.
gulp.task('my-second-task', ['lint', 'bundle'], function() { ... });
Тук, my-second-task
изпълнява функцията за обратно извикване само след lint
и bundle
задачите са изпълнени. Това позволява разделяне на проблемите: Създавате поредица от малки задачи с една отговорност, като преобразуване LESS
до CSS
и създайте вид главна задача, която просто извиква всички останали задачи чрез масива от зависимости на задачите.
И накрая, имаме gulp.watch
, който наблюдава глобален модел на файл за промени и когато се открие промяна, изпълнява поредица от задачи.
gulp.task('my-third-task', function() { gulp.watch('/public/js/**/*.js', ['lint', 'reload']) })
В горния пример всички промени във файл, съответстващи на /public/js/**/*.js
би задействал lint
и reload
задача. Честа употреба на gulp.watch
е да задействате презареждане на живо в браузъра, функция, толкова полезна за разработка, че няма да можете да живеете без нея, след като сте я изпитали.
И точно така разбирате всичко, което наистина трябва да знаете за gulp
.
Когато използвате шаблона CommonJS, групирането на JavaScript файлове не е толкова просто, колкото обединяването им. По-скоро имате входна точка (обикновено се нарича index.js
или app.js
) с поредица от require
или import
изявления в горната част на файла:
var Component1 = require('./components/Component1'); var Component2 = require('./components/Component2');
import Component1 from './components/Component1'; import Component2 from './components/Component2';
Зависимостите трябва да бъдат разрешени преди оставащия код в app.js
и тези зависимости могат да имат допълнителни зависимости за разрешаване. Освен това бихте могли require
една и съща зависимост на няколко места във вашето приложение, но вие искате да разрешите тази зависимост само веднъж. Както можете да си представите, след като имате дърво на зависимости на няколко нива, процесът на групиране на вашия JavaScript става доста сложен. Това е мястото, където пакети като Browserify и Webpack влизат.
Webpack е пакет, докато Gulp е бегач на задачи, така че бихте очаквали да видите тези два инструмента, често използвани заедно. Вместо това има нарастваща тенденция, особено сред общността на React, да се използва Webpack вместо на Гълп. Защо е това?
Просто казано, Webpack е толкова мощен инструмент, че вече може да изпълнява по-голямата част от задачите, които иначе бихте направили чрез бегач на задачи. Например Webpack вече предлага опции за минимизиране и източници за вашия пакет. В допълнение, Webpack може да се изпълнява като междинен софтуер чрез персонализиран сървър, наречен webpack-dev-server
, който поддържа както презареждане на живо, така и горещо презареждане (за тези функции ще говорим по-късно). Използвайки товарачи, можете също да добавите ES6 към ES5 транспилация и CSS предварително и след процесори. Това всъщност просто оставя модулните тестове и свързването като основни задачи, с които Webpack не може да се справи самостоятелно. Като се има предвид, че сме намалили поне половин дузина потенциални гълтачни задачи на две, много разработчици предпочитат вместо това да използват директно NPM скриптове, тъй като това избягва режийните разходи за добавяне на Gulp към проекта (за което също ще говорим по-късно) .
Основният недостатък на използването на Webpack е, че е доста трудно да се конфигурира, което е непривлекателно, ако се опитвате бързо да стартирате и стартирате проект.
Ще създам проект с три различни настройки на бегач на задачи. Всяка настройка ще изпълнява следните задачи:
Нашите три настройки ще бъдат:
Приложението ще използва Реагирайте за предния край. Първоначално исках да използвам фреймворк агностичен подход, но използването на React всъщност опростява отговорностите на бегача на задачи, тъй като е необходим само един HTML файл, а React работи много добре с шаблона CommonJS.
php unicode към utf 8
Ние ще покрием предимствата и недостатъците на всяка настройка, за да можете да вземете информирано решение за това какъв тип настройка най-добре отговаря на нуждите на вашия проект.
Настроих хранилище на Git с три клона, по един за всеки подход ( връзка ). Тестването на всяка настройка е толкова просто, колкото:
git checkout npm prune (optional) npm install gulp (or npm start, depending on the setup)
Нека разгледаме подробно кода във всеки клон ...
- app - components - fonts - styles - index.html - index.js - index.test.js - routes.js
Прав HTML файл. Приложението React се зарежда и ние използваме само един пакет JS и CSS. Всъщност в нашата настройка за разработка на Webpack дори няма да имаме нужда от bundle.css
.
Това действа като JS входна точка на нашето приложение. По същество просто зареждаме React Router в div
с идентификатор app
че споменахме по-рано.
Този файл определя нашите маршрути. URL адресите /
, /about
и /contact
са картографирани на HomePage
, AboutPage
и ContactPage
компоненти, съответно.
Това е поредица от единични тестове, които тестват собственото поведение на JavaScript. В реално приложение за качество на продукцията бихте написали единичен тест за React компонент (поне такива, които манипулират състоянието), тествайки специфично за React поведение. За целите на тази публикация обаче е достатъчно просто да имате функционален единичен тест, който може да работи в режим на гледане.
Това може да се разглежда като контейнер за всички наши изгледи на страници. Всяка страница съдържа както компонент, така и this.props.children
, който изчислява самия изглед на страницата (ex / ContactPage
ако е в /contact
в браузъра).
компоненти / начало / HomePage.js
Това е нашият домашен изглед. Реших да използвам react-bootstrap
тъй като мрежата на bootstrap е отлична за създаване на отзивчиви страници. С правилното използване на bootstrap, броят на медийните заявки, които трябва да напишете за по-малки прозорци, е драстично намален.
Останалите компоненти (Header
, AboutPage
, ContactPage
) са структурирани по подобен начин (react-bootstrap
маркиране, без манипулация на състоянието).
Сега нека поговорим повече за стила.
Моят предпочитан подход за стилизиране на компонентите на React е да има по една таблица със стилове на компонент, чиито стилове са обхванати, за да се прилагат само за този специфичен компонент. Ще забележите, че във всеки от моите React компоненти, най-високото ниво div
има име на клас, съответстващо на името на компонента. Така например, HomePage.js
има своята маркировка, обгърната от:
...
Има и асоцииран HomePage.scss
файл, който е структуриран, както следва:
@import '../../styles/variables'; .HomePage { // Content here }
Защо този подход е толкова полезен? Резултатът е силно модулен CSS, който до голяма степен елиминира проблема с нежеланото каскадно поведение.
Да предположим, че имаме два React компонента, Component1
и Component2
. И в двата случая искаме да заменим h2
размер на шрифта.
/* Component1.scss */ .Component1 { h2 { font-size: 30px; } } /* Component2.scss */ .Component2 { h2 { font-size: 60px; } }
h2
размер на шрифта от Component1
и Component2
са независими, независимо дали компонентите са съседни, или единият компонент е вложен вътре в другия. В идеалния случай това означава, че стилът на компонента е напълно самостоятелен, което означава, че компонентът ще изглежда абсолютно по същия начин, независимо къде е поставен във вашата маркировка. В действителност не винаги е толкова просто, но със сигурност е огромна стъпка в правилната посока.
как да стартирам raspberry pi
В допълнение към стиловете за всеки компонент, обичам да имам styles
папка, съдържаща глобална таблица със стилове global.scss
, заедно с SASS частици, които се справят със специфична отговорност (в този случай, _fonts.scss
и _variables.scss
съответно за шрифтове и променливи). Глобалната таблица със стилове ни позволява да дефинираме общия вид и усещане на цялото приложение, докато помощните частици могат да бъдат импортирани от таблиците със стилове за всеки компонент, ако е необходимо.
Сега, когато общият код във всеки клон е проучен задълбочено, нека преместим фокуса си към първия подход за изпълнение на задачи / пакетиране.
Това излиза до изненадващо голям gulpfile, с 22 вноса и 150 реда код. Така че, за краткост, ще прегледам само js
, css
, server
, watch
и default
задачи в детайли.
// Browserify specific configuration const b = browserify({ entries: [config.paths.entry], debug: true, plugin: PROD ? [] : [hmr, watchify], cache: {}, packageCache: {} }) .transform('babelify'); b.on('update', bundle); b.on('log', gutil.log); (...) gulp.task('js', bundle); (...) // Bundles our JS using Browserify. Sourcemaps are used in development, while minification is used in production. function bundle() { return b.bundle() .on('error', gutil.log.bind(gutil, 'Browserify Error')) .pipe(source('bundle.js')) .pipe(buffer()) .pipe(cond(PROD, minifyJS())) .pipe(cond(!PROD, sourcemaps.init({loadMaps: true}))) .pipe(cond(!PROD, sourcemaps.write())) .pipe(gulp.dest(config.paths.baseDir)); }
Този подход е доста грозен поради редица причини. От една страна, задачата е разделена на три отделни части. Първо, вие създавате вашия Browserify пакет обект b
, като подавате някои опции и дефинирате някои манипулатори на събития. Тогава имате самата задача Gulp, която трябва да предаде имена като функция за обратно извикване, вместо да я вгражда (тъй като b.on('update')
използва същия този обратен разговор). Това едва ли притежава елегантността на задачата за Gulp, при която просто преминавате в gulp.src
и внесете някои промени.
Друг е въпросът, че това ни принуждава да имаме различни подходи за презареждане html
, css
и js
в браузъра. Гледайки нашия Gulp watch
задача:
gulp.task('watch', () => { livereload.listen({basePath: 'dist'}); gulp.watch(config.paths.html, ['html']); gulp.watch(config.paths.css, ['css']); gulp.watch(config.paths.js, () => { runSequence('lint', 'test'); }); });
Когато се промени HTML файл, html
задачата се изпълнява отново.
gulp.task('html', () => { return gulp.src(config.paths.html) .pipe(gulp.dest(config.paths.baseDir)) .pipe(cond(!PROD, livereload())); });
Последните извикващи тръби livereload()
ако NODE_ENV
не е production
, което задейства опресняване в браузъра.
Същата логика се използва и за часовника CSS. Когато се промени CSS файл, css
задачата се изпълнява отново и последната тръба в css
тригери на задачи livereload()
и опреснява браузъра.
Обаче js
watch не извиква js
задача изобщо. Вместо това, манипулаторът на събития на Browserify b.on('update', bundle)
обработва презареждането, използвайки съвсем различен подход (а именно, подмяна на горещ модул). Непоследователността в този подход е дразнеща, но за съжаление е необходима, за да се получи постепенно изгражда. Ако наивно просто извикахме livereload()
в края на bundle
функция, това би пресъздало цял JS пакет за всяка отделна промяна на JS файл. Подобен подход очевидно не се мащабира. Колкото повече JS файлове имате, толкова по-дълго отнема всяко отделяне. Изведнъж вашите 500 ms ребундове започват да отнемат 30 секунди, което наистина възпрепятства пъргавото развитие.
gulp.task('css', () => { return gulp.src( [ 'node_modules/bootstrap/dist/css/bootstrap.css', 'node_modules/font-awesome/css/font-awesome.css', config.paths.css ] ) .pipe(cond(!PROD, sourcemaps.init())) .pipe(sass().on('error', sass.logError)) .pipe(concat('bundle.css')) .pipe(cond(PROD, minifyCSS())) .pipe(cond(!PROD, sourcemaps.write())) .pipe(gulp.dest(config.paths.baseDir)) .pipe(cond(!PROD, livereload())); });
Първият въпрос тук е тромавото включване на CSS на доставчика. Всеки път, когато към проекта се добавя CSS файл на нов доставчик, трябва да не забравяме да променим нашия gulpfile, за да добавим елемент към gulp.src
масив, вместо да добавяме импорта на съответно място в нашия действителен изходен код.
Другият основен въпрос е извитата логика във всяка тръба. Трябваше да добавя NPM библиотека, наречена gulp-cond
просто за настройка на условна логика в моите тръби и крайният резултат не е много четлив (тройни скоби навсякъде!).
gulp.task('server', () => { nodemon({ script: 'server.js' }); });
Тази задача е много ясна. По същество е обвивка около извикването на командния ред nodemon server.js
, която изпълнява server.js
в среда на възел. nodemon
се използва вместо node
така че всякакви промени във файла да го рестартират. По подразбиране, nodemon
ще рестартира текущия процес на всякакви Промяна на JS файл, поради което е важно да включите nodemon.json
файл за ограничаване на обхвата му:
{ 'watch': 'server.js' }
Нека да прегледаме кода на нашия сървър.
const baseDir = process.env.NODE_ENV === 'production' ? 'build' : 'dist'; const port = process.env.NODE_ENV === 'production' ? 8080: 3000; const app = express();
Това задава основната директория на сървъра и порта на базата на средата на възела и създава екземпляр на express.
app.use(require('connect-livereload')({port: 35729})); app.use(express.static(path.join(__dirname, baseDir)));
Това добавя connect-livereload
междинен софтуер (необходим за нашата настройка за презареждане на живо) и статичен междинен софтуер (необходим за обработка на нашите статични активи).
app.get('/api/sample-route', (req, res) => { res.send({ website: 'ApeeScape', blogPost: true }); });
Това е просто прост API път. Ако отидете до localhost:3000/api/sample-route
в браузъра ще видите:
{ website: 'ApeeScape', blogPost: true }
В истински бекенд ще имате цяла папка, посветена на API маршрути, отделни файлове за установяване на DB връзки и т.н. Този примерен маршрут беше просто включен, за да покаже, че можем лесно да изградим бекенд на върха на интерфейса, който сме настроили.
app.get('*', (req, res) => { res.sendFile(path.join(__dirname, './', baseDir ,'/index.html')); });
Това е общ маршрут, което означава, че без значение какъв URL въведете в браузъра, сървърът ще върне нашия самотен index.html
страница. Тогава отговорността на React Router е да разреши нашите маршрути от страна на клиента.
app.listen(port, () => { open(`http://localhost:${port}`); });
Това казва на нашия експресен екземпляр да изслуша посочения от нас порт и да отвори браузъра в нов раздел на посочения URL адрес.
Засега единственото нещо, което не ми харесва в настройката на сървъра, е:
app.use(require('connect-livereload')({port: 35729}));
Като се има предвид, че вече използваме gulp-livereload
в нашия gulpfile това прави две отделни места, където трябва да се използва livereload.
Сега, не на последно място:
gulp.task('default', (cb) => { runSequence('clean', 'lint', 'test', 'html', 'css', 'js', 'fonts', 'server', 'watch', cb); });
Това е задачата, която се изпълнява, когато просто напишете gulp
в терминала. Странността е необходимостта да се използва runSequence
за да накарате задачите да се изпълняват последователно. Обикновено масив от задачи се изпълняват паралелно, но това не винаги е желаното поведение. Например трябва да имаме clean
задачата се изпълнява преди html
за да сме сигурни, че нашите целеви папки са празни, преди да преместите файлове в тях. Когато gulp 4 бъде пуснат, той ще поддържа gulp.series
и gulp.parallel
методи, но засега трябва да напуснем с тази малка странност в нашата настройка.
Освен това, това всъщност е доста елегантно. Цялото създаване и хостинг на нашето приложение се извършва с една команда и разбирането на която и да е част от работния процес е толкова просто, колкото изследването на отделна задача в последователността на изпълнението. Освен това можем да разделим цялата последователност на по-малки парчета за по-подробен подход при създаването и хостването на приложението. Например, бихме могли да настроим отделна задача, наречена validate
който изпълнява lint
и test
задачи. Или може да имаме host
задача, която се изпълнява server
и watch
. Тази способност за организиране на задачи е много мощна, особено когато приложението ви се мащабира и изисква по-автоматизирани задачи.
if (argv.prod) { process.env.NODE_ENV = 'production'; } let PROD = process.env.NODE_ENV === 'production';
Използване на yargs
NPM библиотека, можем да предоставим флагове на командния ред на Gulp. Тук инструктирам gulpfile да настрои средата на възела на производствена, ако --prod
се предава на gulp
в терминала. Нашите PROD
След това променливата се използва като условие за диференциране на поведението за разработка и производство в нашия gulpfile. Например една от опциите, които предаваме на нашите browserify
config е:
plugin: PROD ? [] : [hmr, watchify]
Това казва browserify
да не използвате никакви приставки в производствен режим и използвайте hmr
и watchify
плъгини в други среди.
Това PROD
conditional е много полезен, защото ни спестява от необходимостта да напишем отделен gulpfile за производство и разработка, който в крайна сметка ще съдържа много повторения на кода. Вместо това можем да правим неща като gulp --prod
за да стартирате задачата по подразбиране в производството или gulp html --prod
за да стартирате само html
задача в производството. От друга страна, видяхме по-рано, че затрупваме нашите тръбопроводи за Gulp с изявления като .pipe(cond(!PROD, livereload()))
не са най-четливите. В крайна сметка е въпрос на предпочитание дали искате да използвате подхода на булевата променлива или да настроите две отделни gulpfiles.
Сега нека да видим какво се случва, когато продължаваме да използваме Gulp като наш бегач на задачи, но заменим Browserify с Webpack.
Тестването на прототипа на мобилно приложение може да се направи ръчно или какво?
Изведнъж нашият gulpfile е дълъг само 99 реда с 12 импорта, което е значително намаление от предишната ни настройка! Ако проверим задачата по подразбиране:
gulp.task('default', (cb) => { runSequence('lint', 'test', 'build', 'server', 'watch', cb); });
Сега нашата пълна настройка на уеб приложение изисква само пет задачи вместо девет, драматично подобрение.
Освен това премахнахме необходимостта от livereload
. Нашите watch
задачата сега е просто:
gulp.task('watch', () => { gulp.watch(config.paths.js, () => { runSequence('lint', 'test'); }); });
Това означава, че нашият наблюдател на глътка не задейства какъвто и да е вид поведение на възстановяване. Като допълнителен бонус не е необходимо да прехвърляме index.html
от app
до dist
или build
вече.
Връщайки фокуса си към намаляването на задачата, нашите html
, css
, js
и fonts
всички задачи са заменени с един build
задача:
gulp.task('build', () => { runSequence('clean', 'html'); return gulp.src(config.paths.entry) .pipe(webpack(require('./webpack.config'))) .pipe(gulp.dest(config.paths.baseDir)); });
Достатъчно просто. Изпълнете clean
и html
задачи в последователност. След като приключите, вземете нашата входна точка, прекарайте я през Webpack, като подавате webpack.config.js
файл, за да го конфигурирате, и изпратете получения пакет на нашия baseDir
(или dist
или build
, в зависимост от възела env).
Нека да разгледаме конфигурационния файл на Webpack:
Това е доста голям и плашещ конфигурационен файл, така че нека да обясним някои от важните свойства, зададени в нашия module.exports
обект.
devtool: PROD ? 'source-map' : 'eval-source-map',
Това задава типа на изходните карти, които Webpack ще използва. Webpack не само поддържа изходни карти от кутията, но всъщност поддържа широк спектър от опции за изходни карти. Всяка опция осигурява различен баланс на детайлите на картата на източника спрямо скоростта на възстановяване (времето, необходимо за възстановяване на промените). Това означава, че можем да използваме „евтина“ опция за източник на карта за разработка, за да постигнем бързо презареждане и по-скъпа опция за източник на карта в производството.
entry: PROD ? './app/index' : [ 'webpack-hot-middleware/client?reload=true', // reloads the page if hot module reloading fails. './app/index' ]
Това е нашата входна точка за пакета. Забележете, че е предаден масив, което означава, че е възможно да има множество входни точки. В този случай имаме очакваната точка за влизане app/index.js
както и webpack-hot-middleware
входна точка, която се използва като част от нашата настройка за презареждане на горещ модул.
output: { path: PROD ? __dirname + '/build' : __dirname + '/dist', publicPath: '/', filename: 'bundle.js' },
Тук ще бъде изведен компилираният пакет. Най-объркващата опция е publicPath
. Той задава основния URL адрес за това къде вашият пакет ще бъде хостван на сървъра. Така например, ако вашият publicPath
е /public/assets
, тогава пакетът ще се появи под /public/assets/bundle.js
на сървъра.
devServer: { contentBase: PROD ? './build' : './app' }
Това казва на сървъра коя папка във вашия проект да използва като основна директория на сървъра.
Ако някога се объркате как Webpack картографира създадения пакет във вашия проект към пакета на сървъра, просто запомнете следното:
path
+ filename
: Точното местоположение на пакета в изходния код на вашия проектcontentBase
(като корен, /
) + publicPath
: Местоположението на пакета на сървъраplugins: PROD ? [ new webpack.optimize.OccurenceOrderPlugin(), new webpack.DefinePlugin(GLOBALS), new ExtractTextPlugin('bundle.css'), new webpack.optimize.DedupePlugin(), new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}}) ] : [ new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin() ],
Това са приставки, които подобряват по някакъв начин функционалността на Webpack. Например, webpack.optimize.UglifyJsPlugin
отговаря за минификацията.
loaders: [ {test: /.js$/, include: path.join(__dirname, 'app'), loaders: ['babel']}, { test: /.css$/, loader: PROD ? ExtractTextPlugin.extract('style', 'css?sourceMap'): 'style!css?sourceMap' }, { test: /.scss$/, loader: PROD ? ExtractTextPlugin.extract('style', 'css?sourceMap!resolve-url!sass?sourceMap') : 'style!css?sourceMap!resolve-url!sass?sourceMap' }, gif)(?S*)?$/, loader: 'url?limit=100000&name=img/[name].[ext]', woff2 ]
Това са товарачи. По същество те предварително обработват файлове, които се зареждат чрез require()
изявления. Те донякъде приличат на тръби Gulp, тъй като можете да свързвате товарачите заедно.
Нека разгледаме един от нашите обекти за зареждане:
{test: /.scss$/, loader: 'style!css?sourceMap!resolve-url!sass?sourceMap'}
test
свойството казва на Webpack, че даденият зареждащ файл се прилага, ако файл съвпада с предоставения модел на регулярно изражение, в този случай /.scss$/
loader
свойството съответства на действието, което изпълнява товарачът. Тук ние веригираме заедно style
, css
, resolve-url
и sass
товарачи, които се изпълняват в обратен ред.
Трябва да призная, че не намирам loader3!loader2!loader1
синтаксис много елегантен. В крайна сметка кога някога трябва да четете нещо в програма отдясно наляво? Въпреки това, товарачите са много мощна характеристика на webpack. Всъщност, току-що споменатият товарач ни позволява да импортираме SASS файлове директно в нашия JavaScript! Например, можем да импортираме нашите доставчици и глобални таблици със стилове в нашия файл с входна точка:
import React from 'react'; import {render} from 'react-dom'; import {Router, browserHistory} from 'react-router'; import routes from './routes'; // CSS imports import '../node_modules/bootstrap/dist/css/bootstrap.css'; import '../node_modules/font-awesome/css/font-awesome.css'; import './styles/global.scss'; render(, document.getElementById('app'));
По същия начин в нашия компонент Header можем да добавим import './Header.scss'
за да импортирате свързаната таблица със стилове на компонента. Това важи и за всички наши други компоненти.
Според мен това почти може да се счита за революционна промяна в света на разработката на JavaScript. Няма нужда да се притеснявате за CSS групиране, минимизиране или източници, тъй като нашият товарач обработва всичко това вместо нас. Дори презареждането на горещи модули работи за нашите CSS файлове. Тогава възможността за обработка на импортиране на JS и CSS в един и същ файл прави концептуално по-просто разработване: Повече последователност, по-малко превключване на контекста и по-лесно разсъждение.
За да дадем кратко резюме на това как работи тази функция: Webpack поставя CSS в нашия JS пакет. Всъщност Webpack може да направи това и за изображения и шрифтове:
gif)(?S*)?$/, loader: 'url?limit=100000&name=img/[name].[ext]', ttf)(?S*)?$/, loader: 'url?limit=100000&name=fonts/[name].[ext]'
Зареждащият URL адрес инструктира Webpack да вгради нашите изображения и шрифтове като URL адреси за данни, ако те са под 100 KB, в противен случай ги обслужва като отделни файлове. Разбира се, можем също да конфигурираме размера на границата на различна стойност като 10 KB.
И това е конфигурацията на Webpack накратко. Ще призная, че има доста голям брой настройки, но ползите от използването му са просто феноменални. Въпреки че Browserify има приставки и преобразувания, те просто не могат да се сравняват с Webpack товарачите по отношение на добавената функционалност.
В тази настройка използваме директно npm скриптове, вместо да разчитаме на gulpfile за автоматизиране на нашите задачи.
'scripts': { 'start': 'npm-run-all --parallel lint:watch test:watch build', 'start:prod': 'npm-run-all --parallel lint test build:prod', 'clean-dist': 'rimraf ./dist && mkdir dist', 'clean-build': 'rimraf ./build && mkdir build', 'clean': 'npm-run-all clean-dist clean-build', 'test': 'mocha ./app/**/*.test.js --compilers js:babel-core/register', 'test:watch': 'npm run test -- --watch', 'lint': 'esw ./app/**/*.js', 'lint:watch': 'npm run lint -- --watch', 'server': 'nodemon server.js', 'server:prod': 'cross-env NODE_ENV=production nodemon server.js', 'build-html': 'node tools/buildHtml.js', 'build-html:prod': 'cross-env NODE_ENV=production node tools/buildHtml.js', 'prebuild': 'npm-run-all clean-dist build-html', 'build': 'webpack', 'postbuild': 'npm run server', 'prebuild:prod': 'npm-run-all clean-build build-html:prod', 'build:prod': 'cross-env NODE_ENV=production webpack', 'postbuild:prod': 'npm run server:prod' }
За да стартирате компилации за разработка и производство, въведете npm start
и npm run start:prod
, съответно.
Това със сигурност е по-компактно от нашия gulpfile, като се има предвид, че сме изрязали от 99 до 150 реда код до 19 NPM скрипта или 12, ако изключим производствените скриптове (повечето от които просто отразяват скриптовете за разработка с възловата среда, настроена на продукция ). Недостатъкът е, че тези команди са малко загадъчни в сравнение с нашите аналози на Gulp и не са толкова изразителни. Например, няма начин (поне за който знам) един скрипт npm да изпълнява определени команди последователно, а други паралелно. Това е или едното, или другото.
Този подход обаче има огромно предимство. Чрез използване на NPM библиотеки като mocha
директно от командния ред, не е необходимо да инсталирате еквивалентна обвивка на Gulp за всяка (в този случай gulp-mocha
).
Вместо NPM инсталиране
байпас кредитна карта за проверка на възрастта
Инсталираме следните пакети:
Цитирайки публикацията на Кори Хаус, Защо оставих глътка и мрънкане за NPM скриптове :
Бях голям фен на Гълп. Но при последния ми проект завърших със стотици редове в моя gulpfile и около дузина Gulp плъгини. Бях се борил да интегрирам Webpack, Browsersync, горещо презареждане, Mocha и много други, използвайки Gulp. Защо? Е, някои плъгини имаха недостатъчна документация за моя случай на употреба. Някои приставки разкриха само част от API, от който се нуждаех. Човек имаше странна грешка, при която щеше да гледа само малък брой файлове. Друг лишен цвят при извеждане в командния ред.
Той посочва три основни проблема с Gulp:
Склонен съм да се съглася с всичко това.
Когато библиотека като eslint
се актуализира, свързаните gulp-eslint
библиотеката се нуждае от съответна актуализация. Ако поддръжникът на библиотеката загуби интерес, gulp версията на библиотеката не се синхронизира с родната. Същото важи и за създаването на нова библиотека. Ако някой създаде библиотека xyz
и той хваща, тогава изведнъж се нуждаете от съответния gulp-xyz
библиотека, за да го използвате във вашите глътка задачи.
В известен смисъл този подход просто не се мащабира. В идеалния случай бихме искали подход като Gulp, който може да използва родните библиотеки.
Въпреки че библиотеки като gulp-plumber
помогнете за значително облекчаване на този проблем, въпреки това е вярно, че отчитането на грешки в gulp
просто не е много полезно. Ако дори една тръба изхвърли необработено изключение, получавате проследяване на стека за проблем, който изглежда напълно несвързан с причината за проблема във вашия изходен код. Това може да направи отстраняването на грешки кошмар в някои случаи. Никакво търсене в Google или Stack Overflow наистина не може да ви помогне, ако грешката е достатъчно загадъчна или подвеждаща.
Често намирам, че това е малко gulp
библиотеките са склонни да имат много ограничена документация. Подозирам, че това е така, защото авторът обикновено прави библиотеката предимно за собствена употреба. Освен това е обичайно да се търси документация както за приставката Gulp, така и за самата родна библиотека, което означава много превключване на контекста и два пъти повече четене.
Изглежда ми доста ясно, че Webpack е за предпочитане пред Browserify и NPM скриптовете са за предпочитане пред Gulp, въпреки че всяка опция има своите предимства и недостатъци. Gulp със сигурност е по-изразителен и удобен за използване от NPM скриптовете, но вие плащате цената във всички добавени абстракции.
Не всяка комбинация може да е идеална за вашето приложение, но ако искате да избегнете огромен брой зависимости от разработката и разочароващо преживяване за отстраняване на грешки, Webpack с NPM скриптове е пътят. Надявам се тази статия да ви бъде полезна при избора на подходящите инструменти за следващия ви проект.
Свързани: