Closed sadr0b0t closed 7 years ago
Для начала, апгрейд Nodejs до версии 6 в убунте https://github.com/nodesource/distributions#debmanual
кодовое имя Убунты 16.04 - xenial:
lsb_release -s -c
xenial
добавить ключ через гуй синаптика
https://deb.nodesource.com/gpgkey/nodesource.gpg.key
или командой
curl --silent https://deb.nodesource.com/gpgkey/nodesource.gpg.key | sudo apt-key add -
добавить репозиторий через гуй синаптика
deb https://deb.nodesource.com/node_6.x xenial main
найти пакет nodejs, обновить до последней версии
ReactJS - простой вариант - простое подключение библиотек без левых скриптов
https://reactjs.org/docs/installation.html http://codepen.io/gaearon/pen/rrpgNB?editors=0010
If you prefer to use your own text editor, you can also download this HTML file, edit it, and open it from the local filesystem in your browser. It does a slow runtime code transformation, so don’t use it in production.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
</script>
</body>
</html>
демо-проект без зависимостей и без ноды здесь https://github.com/sadr0b0t/snippets/tree/master/js-web/react-basic
ReactJS - длинный вариант для продакшена с использованием NodeJS (здесь он пока используется только на этапе разработки - в зависимости финальной клиентской части приложения он не попадает) https://reactjs.org/docs/installation.html
Установить скрипты для управления проектом
глобально (рутом)
sudo npm install -g create-react-app
или локально (без рута)
npm install create-react-app
создать проект
с глобальным create-react-app
create-react-app react-basic2
с локальным ./node_modules/.bin/create-react-app react-basic2
cd react-basic2
npm start
веб-приложение откроется в браузере по адресу http://localhost:3000/
Здесь получается, что страничка грузится с локального веб-сервера, запущенного на ноде (его роль - просто отдать эту страничку по адресу и перезагружать ее при изменении исходника на файловой системе). Это дает некоторое удобство при разработке (до тех пор, пока не потребуется вставить свою собственную серверную часть) и (пока) единственный (известный мне) способ для отладки нескомпилированных в оптимизированный JS исходников. К финальному деплою этот тестовый сервер на Node не имеет отношения - мы отлаживаем только клиентскую браузерную часть, а потом ее забираем и водружаем на другой сервер.
Дальше само демо-приложение пишет, что если изменить сохранить App.js, то изменения каким-то образом подгрузятся (вероятно, в браузере):
To get started, edit src/App.js and save to reload
но нифига, после правки js нужно останавливать сервер и запускать заново
решается так: https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-start-doesnt-detect-changes https://webpack.github.io/docs/troubleshooting.html#not-enough-watchers
On Linux and macOS, you might need to tweak system settings to allow more watchers.
cat /proc/sys/fs/inotify/max_user_watches
8192
sudo nano /proc/sys/fs/inotify/max_user_watches
сделал 81920 - заработало (сохраняю файл - браузер обновляется)
Для продакшена типа вариант с компонентами, заранее скомпилированными в чистый JS:
npm run build
Файлик index.html и сопутствующие скомпилированные скрипты появляются в папочке buid, но он как есть локально не открывается, они предлагают его деплоить сразу в корень сайта (в скомпилированных файлах не работают ссылки на ресурсы).
Чтобы открывался локально, в package.json нужно добавить строчку
"homepage" : "./",
npm run build
The project was built assuming it is hosted at the server root. To override this, specify the homepage in your package.json. For example, add this to build it for GitHub Pages:
"homepage" : "http://myname.github.io/myapp",
The build folder is ready to be deployed. You may serve it with a static server:
npm install -g serve serve -s build
Это приём описан в документации https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#building-for-relative-paths
достаточно одной точки без слеша:
If you are not using the HTML5 pushState history API or not using client-side routing at all, it is unnecessary to specify the URL from which your app will be served. Instead, you can put this in your package.json:
"homepage": ".",
This will make sure that all the asset paths are relative to index.html. You will then be able to move your app from http://mywebsite.com to http://mywebsite.com/relativepath or even http://mywebsite.com/relative/path without having to rebuild it.
Короче, в папке build получаем чистого работающего клиента, которого можно открывать хоть локально из файла, хоть водружать на веб-сервер со статикой хоть на сервер приложений с динамикой. Проблема только в том, что отлаживать скомпилированный в классический JS и оптимизированный код в браузере уже не получится (там все ошибки указывают на одну первую строку, в которую реально вытянуто вообще всё приложение). Для отладки или вариант1 (npm start) или еще повнимательнее почитать документацию.
Теперь веб-приложение клиент ReactJS + сервер NodeJS с динамическим взаимодействием.
Будет два отдельных проекта webapp-server - сервер Node.js webapp-client - клиент React
Клиент - обычный проект, сгенерированный с create-react-app (далее немного почищенный). Билдим npm run build, отдаем содержимое на раздачу серверу.
На сервере два запроса API: "/call1" и "/call2" + отдать статикой содержимое клиента.
В проекте сервера делаем симлинк на каталог build из клиента
ln -s ../webapp-client/build client-build
Обновление клиента - выполнить npm run build в каталоге клиента, дальше перезагрузить страничку в браузере, перезапускать сервер не нужно. Главный минус - нет нормальной отладки клиентского кода, т.к. в build попадает оптимизированный JS без синтаксиса React JSX.
Клиентский контент раздаём статикой из папки client-build с serve-static https://github.com/expressjs/serve-static
Сначала вызовы API /call1 и /call2, потом страницы из файлов.
( этот вариант относительно простой, альтернатив до фига, есть еще например https://www.npmjs.com/package/connect)
Последним обработчиком для отдачи 404 для несуществующих ресурсов лучше использовать finalhandler, но здесь минимизируем зависимости - сгенерим вручную https://www.npmjs.com/package/finalhandler
var http = require('http');
var serveStatic = require('serve-static');
var serve = serveStatic('client-build', {'index': ['index.html', 'index.htm']})
http.createServer(function (req, res) {
console.log("request: " + req.url);
switch(req.url) {
case "/call1":
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end("reply for call1");
break;
case "/call2":
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end("reply for call2");
break;
default:
serve(req, res, function(err) {
// better use https://www.npmjs.com/package/finalhandler
// in production instead
if(!err) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.write('<!DOCTYPE html>\n' +
'<html>\n' +
' <head>\n' +
' <meta charset=\'utf-8\'>\n' +
' </head>\n' +
' <body>\n'
);
res.write("404, NOT FOUND: " + req.url);
res.end(
' </body>\n' +
'</html>\n');
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(err);
console.log(err);
}
});
}
}).listen(3000);
console.log('Server running at http://localhost:3000/');
Запускаем сервер
nodejs webapp-server.js
обновляем билд клиента
npm run build
Открываем http://localhost:3000
Видим страницу index.html как надо и получаем первый трап: 1) Во-первых, вызовы API http://localhost:3000/call1 и http://localhost:3000/call2 нифига не работают (вместо них - содержимое index.html) 2) Во-вторых, обновление кода клиентской страницы тоже нифига не работает - приложение показывает все время версию index.html, которая загрузилась в первый раз 3) Если (на чистом браузере) загрузить сначала http://localhost:3000/call1, то можно получить ответ API, но если потом загрузить http://localhost:3000 с index.html и после этого снова вернуться на call1, будет уже только index.html 4) Самое крутое - даже если вырубить сервер, страница все равно загружается и обновляется, как ни в чем ни бывало. 5) Очистка кеша в настройках Фаерфокса не помогает.
При этом, если сервер загружен, на запрос любых ресурсов из строки браузера (те же вызовы api /call1 и /call2 и любые другие) лог сервера печатает
request: /service-worker.js
По этому подлому service-worker.js вышел на объяснение глючной истории.
Оказывается, вместе с компилятором проекта React в обычный JS фейсбуковцы решили упаковать в генератор продакшен-билда заодно технику, которая так и назыается - ServiceWorker (в терминах ректовцев - Progressive Web App). https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app
Типа там всё работает в оффлайне, пиздато добавляется на рабочий стол мобильника с Андроидом, только обновляется по своему усмотрению раз в сутки, а что делать с API вообще не понятно (точнее, можно почитать, но пока лень, начать здесь https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#serving-apps-with-client-side-routing).
чтобы нафиг вырубить https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#opting-out-of-caching
убрать в index.js 2 строки про ServiceWorker (в демо-приложении вообще нах вычищу вместе с файлом)
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
//import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
//registerServiceWorker();
Если приложение уже попало в кеш, в фаерфоксе можно его выгрузить вручную на странице
about:serviceworkers
Дальше ок, клиент обновляется перезагрузкой страницы и дёргает API сервера, когда хочет
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {reply: ''};
}
readServerString(url) {
console.log("read server string: " + url);
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.readyState === 4) { // only if req is "loaded"
if (req.status === 200) { // only if "OK"
this.setState({reply: req.responseText});
} else {
// error
}
}
}.bind(this);
// can't use GET method here as it would quickly
// exceede max length limitation
req.open("POST", url, true);
//Send the proper header information along with the request
req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
req.send();
}
call1() {
console.log("call1");
this.readServerString('/call1');
}
call2() {
console.log("call1");
this.readServerString('/call2');
}
render() {
return (
<div>
<p>
<a onClick={()=>{this.call1()}}>Прочитать с сервера значение 1</a>,
<a onClick={()=>{this.call2()}}>прочитать с сервера значение 2</a>
</p>
<p>
Результат: <span style={{fontStyle: 'italic'}}>{this.state.reply}</span>
</p>
</div>
);
}
}
export default App;
Стоит так же отметить, что в новом реакте окончательно форсят новый синтаксис с классами (React.createClass deprecated, для старого синтаксиса предлагают ставить дополнительным модулем).
С таким синтаксисом важно использовать в onClick функцию, как стрелку - она делает автоматический bind к текущему компоненту (без нее не будет работать this внутри call1 и call2)
см
ES6/Babel version of binding to function of an object https://stackoverflow.com/questions/31617697/es6-babel-version-of-binding-to-function-of-an-object#31628960
бонус: еще пример приложения на React+Node+Express, можно посмотреть чего полезного https://www.nodejsdesignpatterns.com/
всё запулил, полезные проекты:
простейший Ajax https://github.com/sadr0b0t/snippets/tree/master/js-web/node-ajax-demo
React без инфраструктуры reac-scripts https://github.com/sadr0b0t/snippets/tree/master/js-web/react-basic
React и React+Material-UI с инфраструктурой reac-scripts https://github.com/sadr0b0t/snippets/tree/master/js-web/react-basic2
веб-приложение NodeJS+React https://github.com/sadr0b0t/snippets/tree/master/js-web/react-node-webapp
С таким синтаксисом важно использовать в onClick функцию, как стрелку - она делает автоматический bind к текущему компоненту (без нее не будет работать this внутри call1 и call2)
здесь подробнее и больше вариантов https://reactjs.org/docs/handling-events.html
Например, можно определить поле с функцией сразу как стрелку
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
// Warning: this is *experimental* syntax.
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
и тогда в onClick можно передать ссылку на функцию по имени поля, как и раньше
Добавить примеры проектов для чистного клиентского ReactJS и веб-приложения ReactJS+Node.js с ReactJS на клиенте и Node.js на сервере.
Как обычно, использовать минимальное количество зависимостей.