Привет, сообщество разработчиков среднего уровня! Пришло время написать здесь свой первый пост. Сегодня я хотел бы поделиться с вами своим опытом создания расширения Google Chrome DevTools. Недавно я выполнил небольшой внутренний проект для нашей компании и понял, насколько легко и действительно приятно разработать полезное расширение с использованием современных инструментов! Так что, вероятно, этот пост вдохновит вас на создание собственного проекта. Сегодня мы с вами создаем довольно простое приложение, просто чтобы увидеть, насколько это просто. Мы собираемся создать собственные инструменты разработчика Google Chrome, которые будут отображать палитру цветов.

Если вы предпочитаете не тратить время на чтение сообщения, вы можете сразу перейти в репозиторий проекта на Github, переключиться на ветку color-picker и изучить код. В противном случае, давайте начнем с самого начала!

Структурирование проекта

Прежде всего нам нужно подумать о структуре файлов для нашего приложения. Откровенно говоря, вам решать, как организовать свои файлы. Что важно, так это в конце концов, чтобы они были построены в едином каталоге. Итак, у меня будет следующая структура:

src/
- /components/
- — /ColorPicker/
- — — /ColorPicker.jsx
- — — /styles.scss
- — /index.jsx
- /devtools/
- — /devtools.html
- — /devtools.js
- /index.html
.babelrc
gulpfile.babel.js
manifest.json
package.json
webpack.config.js

Теперь мы готовы инициализировать наш проект и установить зависимости. Вы можете сделать это вручную, запустив npm init и установив все зависимости через npm install. Или вы можете просто взять мой package.json файл в качестве примера, поместить его в корневую папку проекта и запустить npm install в консоли.

/* ./package.json */
{
  "name": "ColorPickerDevTools",
  "version": "0.0.1",
  "description": "Example of using React for developing Google Chrome DevTools Extension",
  "scripts": {
    "build": "./node_modules/.bin/gulp build",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://[email protected]:yurist38/chromeDevToolsReactBoilerplate.git"
  },
  "author": "Yuri Drabik <[email protected]>",
  "devDependencies": {
    "autoprefixer": "^7.1.1",
    "babel-core": "^6.25.0",
    "babel-loader": "^7.1.1",
    "babel-polyfill": "^6.23.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "css-loader": "^0.28.4",
    "eslint": "^4.1.1",
    "eslint-plugin-react": "^7.1.0",
    "gulp": "github:gulpjs/gulp#4.0",
    "gulp-rm": "^1.0.4",
    "moment": "^2.18.1",
    "node-sass": "^4.5.3",
    "postcss": "^6.0.6",
    "postcss-cssnext": "^3.0.2",
    "postcss-loader": "^2.0.6",
    "prop-types": "^15.5.10",
    "react": "^15.6.1",
    "react-color": "^2.13.4",
    "react-dom": "^15.6.1",
    "sass-loader": "^6.0.6",
    "style-loader": "^0.18.2",
    "webpack": "^3.0.0"
  }
}

Также, как только наше приложение будет построено, у нас будет ./build каталог с кучей файлов, которые вы можете напрямую установить в свой браузер Chrome в качестве расширения.

Создание приложения React.js

Предлагаю продолжить подготовку нашей React.js заявки. Допустим, у нас есть один дочерний компонент ColorPicker:

/* ./src/components/ColorPicker/ColorPicker.jsx */
import React from 'react';
import PropTypes from 'prop-types';
import { SketchPicker } from 'react-color';
import styles from './styles';
const ColorPicker = ({ color, onChangeColor }) => (
  <div className={styles.container} style={({backgroundColor: color})}>
    <SketchPicker color={color} onChange={onChangeColor} />
  </div>
);
ColorPicker.propTypes = {
  username: PropTypes.string,
  onChangeColor: PropTypes.func
};
export default ColorPicker;

Он включает в себя несколько стилей:

/* ./src/components/ColorPicker/styles.scss */
html, body {
  margin: 0;
  padding: 0;
}
.container {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  min-height: 320px;
  overflow: auto;
}

И, конечно же, нам нужно создать корневой компонент приложения. Я назову его index.jsx и помещу в ./src/components папку.

/* ./src/components/index.jsx */
import React, { Component } from 'react';
import { render } from 'react-dom';
import ColorPicker from './ColorPicker/ColorPicker';
class App extends Component {
  constructor() {
    super();
    this.state = {
      color: '#cccccc'
    }
  }
onChangeColor(color) {
    this.setState({color: color.hex});
  }
render() {
    return (
      <ColorPicker
        color={this.state.color}
        onChangeColor={this.onChangeColor.bind(this)}
      />
    );
  }
}
render(
  <App />,
  document.getElementById('app')
);

Итак, первая часть сделана! Наше замечательное приложение React.js почти готово (конечно, оно очень простое. Используйте его в качестве примера и создавайте свои собственные крутые штуки!).

Пора создать собственное расширение!

Следующим шагом является создание html-страницы, которая будет указана в manifest.json файле как точка входа и загружена в нашу новую панель инструментов разработчика. Это также очень простой, простой скелет HTML, включающий devtools.js скрипт:

/* ./src/devtools/devtools.html */
<!DOCTYPE html>
<html>
  <head>
    <script src="devtools.js"></script>
  </head>
</html>
/* ./src/devtools/devtools.js */
chrome.devtools.panels.create(
  'ColorPicker', // title for the panel tab
  null, // you can specify here path to an icon
  'index.html', // html page for injecting into the tab's content
  null // you can pass here a callback function
);

Манифест

manifest.json - это основной файл декларации нашего расширения. Полное описание формата файла манифеста можно найти здесь. В нашем случае это тоже будет очень коротко и просто:

/* ./manifest.json */
{
  "manifest_version": 2,
"name": "ColorPicker DevTools",
  "description": "Demo extension for Google Chrome browser",
  "version": "1.0",
"permissions": [
    "*://*/*"
  ],
  "devtools_page": "devtools.html"
}

Автоматизировать строительный процесс

И последний шаг - это автоматизация с Webpack и Gulp. Первый, который мы собираемся использовать для транспиляции и объединения нашего React.js приложения.

/* ./webpack.config.js */
const path = require('path');
module.exports = {
  entry: [
    path.resolve('./src/components/index.jsx')
  ],
  output: {
    path: path.resolve('./build/'),
    filename: 'app.js',
    publicPath: '.'
  },
  module: {
    loaders: [
      {
        test: /\.jsx/,
        include: path.resolve('./src/components/'),
        loader: 'babel-loader'
      },
      {
        test: /\.s?css$/,
        use: [
          {loader: 'style-loader'},
          {
            loader: 'css-loader?importLoaders=1',
            query: {
              modules: true,
              localIdentName: '[name]__[local]___[hash:base64:5]'
            }
          },
          {loader: 'sass-loader'},
          {
            loader: 'postcss-loader',
            options: {
              plugins: function () {
                return [
                  require('postcss-cssnext')
                ];
              }
            }
          }
        ]
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.jsx', '.scss', '.css']
  }
};

Для корректной обработки синтаксиса ES6 нам нужно указать пресет babel в ./.babelrc файле в корневой папке:

/* ./.babelrc */
{
  "presets": ["es2015", "react"]
}

И, наконец, мы будем использовать gulp для обслуживания всех необходимых файлов вместе в папке ./build/. Напишем несколько задач:

/* ./gulpfile.babel.js */
import gulp from 'gulp';
import rm from 'gulp-rm';
import webpack from 'webpack';
import fs from 'fs';
const paths = {
  build: './build'
};
/* CLEAN */
gulp.task('clean', () => {
  return gulp
    .src(`${paths.build}/**/*`, {read: false})
    .pipe(rm());
});
/* BUILD COMPONENTS */
gulp.task('build:app', (done) => {
  webpack(require('./webpack.config.js'), done);
});
/* COPY FILES INTO BUILD FOLDER */
const files = [
  './manifest.json',
  './src/index.html',
  './src/devtools/devtools.html',
  './src/devtools/devtools.js'
];
gulp.task('copy:static', (done) => {
  gulp
    .src(files)
    .pipe(gulp.dest(paths.build))
    .on('end', done);
});
/* BUILD */
gulp.task('build', gulp.series([
  'clean',
  'build:app',
  'copy:static'
]));

Вот и все. Наше собственное расширение devtools готово! Как только вы закончите withnpm build, вы можете установить расширение в свой браузер. Простую инструкцию, как это сделать, можно найти в обзоре репо.

Демо

Заключение

Как я уже упоминал ранее, это всего лишь базовый пример. Я продолжаю учиться писать расширения для Chrome с помощью современных инструментов, таких как React.js. И если вам, ребята, тоже интересно узнать об этом больше, дайте мне знать в комментариях. Для меня это будет поводом поделиться другими примерами, а также передовыми методами.

Исходный код

Github.com/yurist38/chromeDevToolsReactBoilerplate/tree/color-picker

Всем счастливых выходных!